Extends built-in Three.JS materials to support infinite, non-repeating, seamless texture tiling.
Live interactive demo: https://three-hex-tiling.ameo.design/
npm install three-hex-tiling
Then, to enable it, just add this to your project as early as possible:
import 'three-hex-tiling';
This import will patch Three.JS's shaders and materials to support the hex tiling algorithm and it will extend the types for the patched materials with parameters to control it.
This library has been tested with Three.JS versions 0.151
to 0.168
. Although it may work with other versions, support is not guaranteed.
After adding the three-hex-tiling
import at the top of your project, a new hexTiling
parameter is added to the parameters of supported materials. If your project uses TypeScript, these should be included in the types you see when creating those materials.
By setting the hexTiling
property when creating a material, hex tiling will be enabled for that material. It is disabled by default.
const mat = new THREE.MeshStandardMaterial({
map: myTexture,
normalMap: myTextureNormalMap,
roughnessMap: myTextureRoughnessMap,
hexTiling: {
// default values shown
patchScale: 2,
useContrastCorrectedBlending: true,
lookupSkipThreshold: 0.01,
textureSampleCoefficientExponent: 8,
}
});
Hex tiling cannot be enabled or disabled after a material is created, but the values of individual parameters can be changed dynamically:
mat.hexTiling.patchScale = newPatchScale;
When enabling hex tiling for a material, you may find that your textures need a scale adjustment to look optimal. This can be done using built-in Three.JS texture scaling support:
myTexture.scale.set(1.5, 1.5);
myTextureNormalMap.scale.set(1.5, 1.5);
myTextureRoughnessMap.scale.set(1.5, 1.5);
myTexture.needsUpdate = true;
myTextureNormalMap.needsUpdate = true;
myTextureRoughnessMap.needsUpdate = true;
Textures used with three-hex-tiling
must be seamless - meaning that there are no sharp cutoffs when the texture is tiled. There's a good chance your textures are seamless already and if they aren't, it will be obvious.
The following materials are currently supported for use with three-hex-tiling
:
MeshStandardMaterial
MeshPhysicalMaterial
You can still use all of the other materials that Three.JS provides, but they will not have support for hex tiling.
In addition to the base texture/color of a material provided in the map
property, three-hex-tiling
supports with the following maps:
normalMap
roughnessMap
metalnessMap
three-hex-tiling
accepts the following configuration properties in the hexTiling
object:
- Description: Scale factor for the hexagonal tiles used to break up the texture. This parameter is crucial in controlling the hex tiling's appearance and requires adjustment for each texture.
-
Default:
2
-
Range:
[0, Infinity]
, typically between 0.1 and 8. Optimal values depend on the texture and desired effect. - Behavior: Larger values create smaller hexagonal tiles, resulting in more texture breakup.
Patch Scale: 1 | Patch Scale: 2 | Patch Scale: 6 |
- Description: Determines if contrast-corrected blending is used for texture samples. This method often enhances blending quality but might result in overly bright or dark patches in high-contrast textures.
-
Default:
true
- Reference: ShaderToy Demo
Contrast-Corrected Blending: Enabled | Contrast-Corrected Blending: Disabled |
- Description: The minimum magnitude below which texture lookups are skipped, mainly for optimization purposes.
-
Default:
0.01
-
Range:
[0, 1]
(but you'll probably always want to keep it <0.1) - Advice: Usually doesn't require modification.
- Details: The shader mixes up to three texture samples per fragment. Texture lookups with a final coefficient less than this threshold are skipped to reduce GPU memory bandwidth usage.
-
Description: The exponent for texture sample coefficients before comparison with
lookupSkipThreshold
. Adjusting this value affects shader efficiency and the visibility of hexagonal tile borders. -
Default:
8
-
Range:
(0, 64]
- Advice: The default value is suitable for most textures. Modification is usually unnecessary.
- Details: Coefficients raised to this exponent modify the steepness of the threshold for skipping texture lookups. Higher exponents increase efficiency by reducing texture lookups, potentially making the shader more efficient.
Texture Sample Coefficient Exponent: 1 | Texture Sample Coefficient Exponent: 2 | Texture Sample Coefficient Exponent: 8 |
The hex tiling shader used by this library needs to make up to 3 texture fetches per map per fragment in order to function.
Usually, this is fine and doesn't result in any noticeable performance hit. But in some situations, hex tiling can create a significant amount of texture bandwidth usage on the GPU and impact performance on weaker devices.
There are some ways to tune three-hex-tiling
to lessen its performance impact:
- Increase
textureSampleCoefficientExponent
and/orlookupSkipThreshold
- This directly reduces the average number of texture samples made per fragment, but it can make the borders between hex tiles more obvious.
- Use a depth pre-pass to your scene to reduce the number of calls to the fragment shader.
- For some scenes, especially those with high overdraw, this can be a big win
- Reduce the number of maps used by your material
- Reduce the size of textures used or use compressed textures
The hex tiling shader itself is adapted from a Shadertoy by Fabrice Neyret.
three-hex-tiling
works by modifying Three.JS's shaders directly, patching in the hex tiling algorithm and conditionally enabling it for materials that opt in. Materials that do not explicitly set hexTiling
will work normally.
In addition to patching the shaders, it also installs a custom onBeforeCompile
callback on materials. If you make use of onBeforeCompile
in your own code, there's a good chance that three-hex-tiling
will interfere with it and cause problems.