MIT License NuGet

Home / Features / Materials and PBR

Materials and PBR

Materials come from glTF and stay in glTF's model: metallic-roughness, with normal, occlusion and emissive maps. There is no material editor and no shader graph — the surface you authored in Blender is the surface the engine renders.

One shading model

Every opaque and transparent surface goes through the same metallic-roughness BRDF. That is a deliberate narrowing: no material permutation matrix, no shader variants to warm up, and an asset that looks right in a glTF viewer looks right here.

InputHandling
Base colour + factorsRGB texture, multiplied by the per-material factor and per-instance colour.
Metallic / roughnessPacked glTF channels, sampled directly; no remap layer.
Normal mapTangent-space, with optional Toksvig normal-variance mips to tame specular aliasing.
OcclusionApplied to indirect diffuse only, so it does not double-count with GTAO.
EmissiveAdded after lighting, unaffected by shadows and AO.
AlphaOpaque, mask and blend modes; blended geometry is drawn after opaque in the same pass.
UnlitModel.Unlit bypasses lighting entirely — useful for debug markers and UI-in-world.

Overriding what the file said

An imported material is a starting point, not a contract. Every map and factor has an override on Model, so a shared asset can be recoloured or re-textured per instance without touching the GLB.

PropertyTypeReplaces
MaterialColorVector4?Whole-model colour multiplier, applied on top of the base colour factor.
BaseColorOverrideTextureUpdateSourceThe base colour map.
NormalOverrideTextureUpdateSourceThe normal map.
MetallicRoughnessOverrideTextureUpdateSourceThe packed metallic-roughness map.
OcclusionOverrideTextureUpdateSourceThe ambient occlusion map.
EmissiveTextureOverrideTextureUpdateSourceThe emissive map.
MetallicOverridefloat?The metallic factor.
RoughnessOverridefloat?The roughness factor.
EmissiveFactorOverrideVector4?The emissive factor.
UnlitboolSkips lighting for the whole model.

Overrides are nullable or empty by default, which means "use whatever the file says". A texture override takes a TextureUpdateSource, so the replacement can be a file, a decoded buffer, or a texture another effect produced.

Texture sampling defaults

Sampling quality is a project-wide decision rather than a per-material one, so it lives on RenderQuality.

SettingDefaultEffect
TextureMipmapsonGenerates the mip chain at upload.
TextureMipMinSize64Stops generating mips below this edge length.
TextureMaxAnisotropy16Anisotropic filter taps, clamped to what the device reports.
TextureNormalVarianceonToksvig-style roughness widening in normal-map mips.
TextureLodBias-0.5Slight sharpening bias; TAA absorbs the extra aliasing.

The negative LOD bias and normal-variance mips are paired on purpose. Biasing towards sharper mips reintroduces specular sparkle, and widening roughness where the normal map is busy is what pays for it. Turn one off and you probably want to turn the other off too.

HDR and exposure

Scene colour is HDR by default. Lighting accumulates in linear space at floating-point precision, and the Post pass applies exposure and tonemapping on the way to the swapchain — which is what lets bloom threshold against real radiance values instead of clipped ones.

SettingDefaultNotes
HdrSceneColoronOff falls back to an 8-bit scene target; bloom and TAA quality drop with it.
HdrExposure1.0Linear multiplier applied before the tonemap curve.
KhrLightIntensityScale0.05Scales punctual lights imported from glTF, whose photometric units are far larger than the engine's.

KhrLightIntensityScale exists because KHR_lights_punctual specifies intensity in candela and lux, and a light exported at those magnitudes will blow out a scene lit in engine units. A model can further scale its own imported lights with Model.LightIntensityScale.

Ambient from an environment map

Constant ambient is the default and the cheapest option. When a scene deserves better, EnvironmentMap loads six cube faces and projects them into spherical harmonics for diffuse ambient, optionally keeping the radiance cube for specular.

ModeWhat is applied
OffConstant ambient from SceneLighting.Ambient only.
DiffuseNine-coefficient irradiance SH replaces the constant ambient term.
DiffuseSpecularAs above, plus the radiance cube for specular ambient.

Cube faces load as Rgba8Unorm or Rgba16Float; SkyIntensity and DiffuseIntensity scale the two contributions independently, which is the usual way to keep an HDRI from over-lighting a scene that also has a sun.

Known gaps

  • Absent Texture compression. Textures upload uncompressed today. This is a named roadmap item, not an oversight — see Track A.
  • Absent Clearcoat, sheen, transmission and the other glTF material extensions. The core metallic-roughness set is what is implemented.
  • Absent Custom shaders per material. Extending the shading model means editing the shared shader, not authoring a new one from application code.