Resources Numerical accuracy Calculation policy

Imported material parameters

Turns a glTF or FBX material's numbers (roughness, index of refraction, film thickness, emission strength) into the renderer's parameters and back.

How the precision is kept

Each parameter is copied or converted in 64-bit, and every number in the result is then rounded to 7 significant digits with toPrecision(7). The values describe how a surface is shaded; they are kept on the import report.

Open item. Keep the converted parameters unrounded; toPrecision(7) drops digits the source file had.

Calculations under this policy

4 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

io/pbr.js ModelCraft 0.8.1 64-bit 4 calculations Reviewed

Tests tools/modelcraft-test/gltf.mjs

Rounding of every parameter

64-bit

Rounds each number of a converted material.

v → the number nearest v with 7 significant digits

Number(v.toPrecision(7)) is applied to every numeric parameter and to each number of every list before the material is returned. This rounds the stored value, not a display.

  • io/pbr.js
  • line 31
  • ModelCraft 0.8.1
const round = (v) => (Array.isArray(v) ? v.map(round) : typeof v === 'number' ? Number(v.toPrecision(7)) : v);

Film thickness and dispersion

64-bit

A thin film's thickness in micrometers and a glass's Abbe number.

thickness (µm) = maximum thickness (nm) ÷ 1000; Abbe number = 20 ÷ dispersion

A division by the whole number 1000 and one by the file's dispersion, which is checked to be above 0 first.

  • io/pbr.js
  • lines 80 to 84
  • ModelCraft 0.8.1
  p.thin_film_thickness = m.iridescence.thicknessMax / 1000;
  p.thin_film_ior = m.iridescence.ior;
}
if (m.anisotropy && m.anisotropy.strength > 0) p.specular_roughness_anisotropy = clamp01(m.anisotropy.strength);
if (m.dispersion > 0) { p.transmission_dispersion_scale = 1; p.transmission_dispersion_abbe_number = 20 / m.dispersion; }

Emission strength

64-bit

Splits an emissive color into a color with a peak of 1 and a luminance.

peak = largest channel; color = channels ÷ peak; luminance = peak × strength

The division by the peak runs only when at least one channel is above 0, so it is guarded.

  • io/pbr.js
  • lines 87 to 89
  • ModelCraft 0.8.1
const peak = Math.max(...m.emissive);
p.emission_color = ap1(m.emissive.map((c) => c / peak));
p.emission_luminance = peak * strength;

Shininess to roughness

64-bit

A roughness from a Blinn–Phong shininess exponent.

roughness = (2 ÷ (s + 2))^¼, clamped to [0, 1]

Two square roots of a quotient whose denominator is at least 2, because the exponent is floored at 0. The code's comment names it the usual published conversion.

  • io/pbr.js
  • lines 147 to 148
  • ModelCraft 0.8.1
const s = Math.max(0, n('ShininessExponent') ?? n('Shininess'));
p.specular_roughness = clamp01(Math.sqrt(Math.sqrt(2 / (s + 2))));

In detail

Method
Direct mappings between the two material models; a shininess exponent becomes a roughness by a published conversion.
Number types
64-bit numbers, rounded to 7 significant digits at the end.
Tolerances
An emission strength within 1e-6 of 1 is not written to glTF. An emission peak is floored at 1e-9 on the way back to glTF.
Limits
Every parameter keeps 7 significant digits.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach