























One of the long-standing frustrations with MaterialX is that the format is well-specified on paper but wildly inconsistent in practice. Open a .mtlx file in the reference viewer, then open the same file in Three.js or Blender, and you get something that looks vaguely related — but not the same. Colors shift. Noise patterns break. Procedural textures come out completely wrong.
![]()
I wanted to fix that. This is the story of how I did it — twice, in parallel, for two very different rendering environments.
The catalyst was the Khronos glTF Render Fidelity suite. That project compared glTF rendering across multiple real-time and photorealistic renderers — including V-Ray and Blender — using a shared set of sample assets. It was remarkably effective. Renderer authors could see exactly where they diverged from the reference, and having objective evidence drove real improvements. It raised the baseline quality of glTF support across the entire ecosystem.
![]()
I wanted to repeat that for MaterialX. No comparable suite existed. You could look at isolated examples, but there was no systematic, side-by-side comparison across renderers that made regressions and gaps immediately visible. Without that, you are guessing.
So I built one: the MaterialX Fidelity Suite.
It started with a couple hundred test materials and has grown to over 400. Each material is designed to test specific aspects of the format in relative isolation — individual node types, surface models, parameter combinations, procedural patterns — rather than complex, real-world scenes that make it hard to identify what broke. The suite covers:
standard_surface, gltf_pbr, and open_pbr_surfaceEvery material is rendered through the reference materialxview tool from the MaterialX project itself. That render is the ground truth. Every other renderer is compared against it, side by side, and a visual difference metric is computed automatically so regressions are immediately visible as numbers — not just impressions.
One complication became clear early: the renderers in the suite use fundamentally different techniques, and those differences produce unavoidable visual divergence that has nothing to do with MaterialX correctness.
materialxview — the reference — is a ray tracerThis means some visual differences between renderers are expected and correct — a path tracer will show inter-reflections and indirect lighting that a rasterizer simply cannot produce. The fidelity suite compares everything against materialxview, so those structural differences show up in the metrics. The goal was never to eliminate them; it was to minimize the avoidable differences — the ones caused by incorrect node implementations, wrong coordinate spaces, or broken noise functions — so that what remains is only the fundamental rendering-technique gap.
![]()
This distinction matters when reading the metrics. A Three.js result that scores slightly lower than a Blender Cycles result is not necessarily worse — it is partly a consequence of rasterization versus path tracing. The suite makes this visible and lets you distinguish the two categories of difference.
The fidelity suite is what made everything else possible. Without it, I would not have known what was broken or when I had actually fixed it.
My familiarity with Three.js made it the natural starting point. The library already had a MaterialX loader, but it had accumulated significant divergence from the reference. I worked through the test suite systematically, using the visual comparisons to guide fixes.
The number of issues was larger than expected:
mx_ifgreater, mx_ifgreatereq, and mx_ifequal condition nodes were producing the opposite of the intended result.rotate2d, rotate3d, and place2d were all reimplemented to match reference behavior.mx_* functions and their default values.I also introduced new features in the same PR: support for the open_pbr_surface and gltf_pbr surface models, archive loading (.mtlx.zip files from AMD Material Library and Poly Haven work directly), a modular node registry design, and proper handling of default values.
The key challenge in Three.js, as it turned out, was the noise functions. Three.js had similar noise primitives to MaterialX, but "similar" is not the same as "identical." A slightly different hash, a slightly different parameter space, or a different approach to octave accumulation, and the whole output changes. I imported the noise implementations from the MaterialX reference library and ported them faithfully into the Three.js GLSL shaders. That is what closed the gap.
The result: near-perfect MaterialX reproducibility across all 400+ samples, with the sole remaining outlier being hextiledimage, which requires a more specialized solution.
The PR is open here: Three.js PR #33485 — WIP: MaterialX upgrade
Blender was a harder problem — and a more interesting one.
The approach had to be fundamentally different. Three.js uses GLSL shaders, so fixing it meant writing better shader code. Blender uses a visual node graph, and a MaterialX graph does not map one-to-one onto a Blender node graph. The importer has to be a compiler: it reads the MaterialX document and constructs an equivalent Blender material that reproduces the visual result, even if the graph structure looks nothing like the original.
I wrote a Python importer — blender-materialx-importer — that uses the MaterialX Python library already compiled into Blender to read .mtlx files, then constructs equivalent Blender node graphs.
A few things make this non-trivial:
place2d node, for example, requires a chain of Blender math and vector nodes to reproduce correctly.After getting the importer working and running it through the fidelity suite, I found that the vast majority of nodes reproduced well. The exception was the noise functions — the same problem I had hit in Three.js, but harder to fix.
MaterialX's procedural noise library is central to a huge range of materials: marble, wood, brick, stone, fabric, cloud. If the noise is wrong, an entire category of materials breaks. And Blender's built-in noise nodes, while powerful, are not the same implementations as MaterialX's.
The differences are subtle but consequential:
![]()
The only way to get correct results was to implement the MaterialX noise functions natively in Blender's shader languages.
I wrote pixel-perfect implementations of the MaterialX noise functions for both Cycles (in OSL and GLSL) and submitted them as a Blender core PR: Blender PR #158054 — MaterialX Noise Nodes.
This adds native Blender shader nodes that exactly match the MaterialX reference:
ShaderNodeMxNoise2D / ShaderNodeMxNoise3DShaderNodeMxFractal2D / ShaderNodeMxFractal3DShaderNodeMxCellNoise2D / ShaderNodeMxCellNoise3DShaderNodeMxWorleyNoise2D / ShaderNodeMxWorleyNoise3DShaderNodeMxUnifiedNoise2D / ShaderNodeMxUnifiedNoise3D![]()
![]()
The Python importer detects whether these nodes are available at runtime. If they are, it uses them and gets exact results. If they are not (i.e., on a stock Blender build), it falls back to Blender-native approximations and records a warning.
This design means the importer is useful right now, and becomes progressively better as the custom nodes are available — in the meantime, users can patch their own Blender build from the PR.
Looking back, both implementations converged on the same root cause and the same fix: the noise functions.
MaterialX's noise implementations are specific. They are not interchangeable with "similar" noise in another system. The hash functions, the domain transformations, the way octaves are accumulated — all of it has to match the reference exactly, or the output diverges. The visual difference between a correct and an incorrect noise implementation can range from subtle to completely unrecognizable.
The efficiency of working on both simultaneously was real. The insights from debugging noise in GLSL for Three.js transferred directly to the OSL/GLSL implementations for Blender. The fidelity suite made both efforts measurable and kept regressions visible at every step.
Across 400+ MaterialX samples:
hextiledimage.This is a qualitative leap from where things were. Previously, MaterialX in Three.js or Blender meant a handful of textures and a few base parameters wired up. Now it means the full graph — procedural textures, coordinate transforms, compositing, surface model variants — reproduced faithfully.
For Three.js users: Drop in a .mtlx or .mtlx.zip file and get a real-time render that matches what the reference viewer produces. This works today, pending the PR merge.
For Blender users: Import any MaterialX material — from AMD Material Library, Poly Haven, or your own pipeline — and get a Blender Cycles material that reproduces the reference render. If you build Blender with PR #158054, the noise-based procedural materials also match.
For the ecosystem: The MaterialX Fidelity Suite is open for other renderers to add their results. If you are building a MaterialX importer, you can benchmark against the same reference and see exactly where you stand.
MaterialX is a powerful, expressive standard for graph-based materials. It deserves tooling that actually delivers what the format promises. This is a step toward that.
This work is sponsored by Land of Assets.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。