Proposal: MaterialX Flake3d Node
MaterialX Flake3d Node Proposal
Summary
Flake generation provides a procedural method to create sparkling, metallic flake effects commonly seen in automotive paints, cosmetics, and other materials with embedded reflective particles. This document proposes a new MaterialX node <flake3d> based on the algorithm developed by Matthias Raab that generates procedurally distributed flake normals with controllable size, roughness, and coverage.
This node is particularly valuable for creating realistic car paint materials, glittery surfaces, and other effects that require procedural generation of micro-scale reflective particles without requiring high-resolution texture maps.
Key Features
The <flake3d> node provides the following key features:
- Procedural generation: Creates infinite variations of flake patterns without requiring texture assets
- Physically-based distribution: Uses microfacet theory (GGX) for realistic normal perturbation
- Efficient evaluation: Optimized algorithm suitable for real-time rendering
- Artistic control: Intuitive parameters for size, roughness, and coverage density
Parameters
<flake3d>
| Parameter | Type | Range | Default | Description |
|---|---|---|---|---|
| size | float | [0.0, 5.0] | 0.01 | Size of individual flakes |
| roughness | float | [0.0, 1.0] | 0.1 | Roughness of the flake normal distribution |
| coverage | float | [0.0, 1.0] | 0.5 | Density of flake coverage (0 = no flakes, 1 = full coverage) |
| position | vector3 | Pworld |
3D position for flake generation | |
| normal | vector3 | Nworld |
Base surface normal | |
| tangent | vector3 | Tworld |
Surface tangent vector | |
| bitangent | vector3 | Bworld |
Surface bitangent vector |
Outputs
The node returns multiple outputs to enable advanced shading workflows:
| Output | Type | Range | Description |
|---|---|---|---|
| id | int | [0, INT32_MAX] | Unique identifier for each flake (0 for no flake) |
| rand | float | [0.0, 1.0] | Random value per flake for additional variation (0 for no flake) |
| presence | float | [0.0, 1.0] | Depth-based presence value per flake for additional variation (0 for no flake) |
| normal | vector3 | The computed flake normal (base normal if no flake present) |
Examples
Outputs
| rand | presence | normal |
Basic Flake Material
<flake3d name="flakes" type="multioutput">
<input name="size" type="float" value="0.01"/>
<input name="roughness" type="float" value="0.2"/>
<input name="coverage" type="float" value="0.5"/>
</flake3d>
<standard_surface name="surface">
<input name="base" type="float" value="0.1"/>
<input name="base_color" type="color3f" value="0.06, 0.424, 0.679"/>
<input name="metalness" type="float" value="1.0"/>
<input name="specular_roughness" type="float" value="0.2"/>
<input name="coat" type="float" value="0.5"/>
<input name="coat_roughness" type="float" value="0.05"/>
<input name="normal" type="vector3" nodename="flakes" output="normal"/>
</standard_surface>
| size = 0.01 | size = 0.1 | size = 1.0 |
| roughness = 0.05 | roughness = 0.2 | roughness = 0.5 |
| coverage = 0.1 | coverage = 0.5 | coverage = 1.0 |
This is excellent, thanks for contributing! I love having all of those useful outputs, this is really great.
Would it make sense to call this flake3d, since it works with 3d positions? Have there been discussions on a 2d version that works from texture coordinates, using this new implementation?
That makes sense as the current algorithm requires 3D space. Updated the description. Thanks!
We haven’t discussed 2D support yet. Sharing code with a 2D version would be difficult if we add it, so calling this “3D” seems reasonable.
@crydalch Matthias told me that it actually gives acceptable result with texture coordinates, using (u, v, 0) for example (I tested). Maybe we could call this <flake>, without "3d"?
My vote would be to call it flake3d anyway. It would fit more nicely with other procedural nodes, such as noise etc. It we can trivially add flake2d at the same time, but having that node use the 3D version and just pass (u, v, 0) then great. I think the naming leaves us opportunities to optimize different backends in different ways later too.
Curious why you wouldn't consider this a shader? What are the benefits of adding it to MaterialX as opposed to writing an OSL shader? Although the general functionality of having a Scatter type node in mtlx might be useful. Where you could hook any 'shape' and then trans/rot/scale, blend, colorize a coverage distribution. Could be a set of predefined distributions(linear, ramp, radial, bevel) or texture driven. Is there anything in the Flake3D node, that you couldn't create with that type of scatter? But perhaps even the Scatter should simply be a shader?
Because generalized scattering (aka “bombing”) on arbitrary shape is very slow. To achieve a metallic‑flake look, you’d need to generate a large number of instances, which would result in unacceptable performance. We need this to run at interactive speed.