Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion libraries/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,4 @@ This folder contains the standard data libraries for MaterialX, providing declar
- point, directional, spot
- Shader generation does not currently support:
- `displacementshader` and `volumeshader` nodes for hardware shading targets (GLSL, MSL).
- `hextiledimage` and `hextilednormalmap` for OSL and MDL.
- `blur` the implementation passes through `in` unmodified in all shading languages.
176 changes: 176 additions & 0 deletions libraries/stdlib/genosl/lib/mx_hextile.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// https://www.shadertoy.com/view/4djSRW
vector2 mx_hextile_hash(vector2 p)
{
vector pp3 = fmod(vector(p.x, p.y, p.x) * vector(0.1031, 0.1030, 0.0973), 1.0);
pp3 += dot(pp3, vector(pp3[1], pp3[2], pp3[0]) + 33.33);
return fmod(vector2(pp3[0] + pp3[1], pp3[0] + pp3[2]) * vector2(pp3[2], pp3[1]), 1.0);
}

// Christophe Schlick. "Fast Alternatives to Perlin's Bias and Gain Functions".
// In Graphics Gems IV, Morgan Kaufmann, 1994, pages 401-403.
// https://dept-info.labri.fr/~schlick/DOC/gem2.html
float mx_schlick_gain(float x, float r)
{
float rr = clamp(r, 0.001, 0.999); // to avoid glitch
float a = (1.0 / rr - 2.0) * (1.0 - 2.0 * x);
return (x < 0.5) ? x / (a + 1.0) : (a - x) / (a - 1.0);
}

struct HextileData
{
vector2 coords[3];
vector weights;
vector rotations;
};

// Helper function to compute blend weights with optional falloff
vector mx_hextile_compute_blend_weights(vector luminance_weights, vector tile_weights, float falloff)
{
vector w = luminance_weights * pow(tile_weights, 7.0);
w /= (w[0] + w[1] + w[2]);

if (falloff != 0.5)
{
w[0] = mx_schlick_gain(w[0], falloff);
w[1] = mx_schlick_gain(w[1], falloff);
w[2] = mx_schlick_gain(w[2], falloff);
w /= (w[0] + w[1] + w[2]);
}
return w;
}

// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
// http://jcgt.org/published/0011/03/05/
HextileData mx_hextile_coord(
vector2 coord,
float rotation,
vector2 rotation_range,
float scale,
vector2 scale_range,
float offset,
vector2 offset_range)
{
float sqrt3_2 = sqrt(3.0) * 2.0;

// Scale coord to maintain the original fit
vector2 st = coord * sqrt3_2;

// Skew input space into simplex triangle grid
// (1, 0, -tan(30), 2*tan(30))
vector2 st_skewed = vector2(st.x + st.y * -0.57735027, st.y * 1.15470054);

// Barycentric weights
vector2 st_frac = fmod(st_skewed, 1.0);
// Handle negative coordinates properly
if (st_frac.x < 0.0) st_frac.x += 1.0;
if (st_frac.y < 0.0) st_frac.y += 1.0;

vector temp = vector(st_frac.x, st_frac.y, 0.0);
temp[2] = 1.0 - temp[0] - temp[1];

float s = step(0.0, -temp[2]);
float s2 = 2.0 * s - 1.0;

float w1 = -temp[2] * s2;
float w2 = s - temp[1] * s2;
float w3 = s - temp[0] * s2;

// Vertex IDs
int base_id_x = int(floor(st_skewed.x));
int base_id_y = int(floor(st_skewed.y));
int si = int(s);
int id1_x = base_id_x + si;
int id1_y = base_id_y + si;
int id2_x = base_id_x + si;
int id2_y = base_id_y + 1 - si;
int id3_x = base_id_x + 1 - si;
int id3_y = base_id_y + si;

// Vertex IDs as array for looping
int id_x[3] = { id1_x, id2_x, id3_x };
int id_y[3] = { id1_y, id2_y, id3_y };

// Tile centers, random values, and offsets
vector2 ctr[3], rand[3], offsets[3];
vector2 seed_offset = vector2(0.12345, 0.12345); // To avoid some zeros
vector2 rr = vector2(radians(rotation_range.x), radians(rotation_range.y));
vector rotations, scales;

for (int i = 0; i < 3; i++)
{
ctr[i] = vector2(float(id_x[i]) + float(id_y[i]) * 0.5, float(id_y[i]) / 1.15470054) / sqrt3_2;
rand[i] = mx_hextile_hash(vector2(float(id_x[i]), float(id_y[i])) + seed_offset);
rotations[i] = mix(rr.x, rr.y, rand[i].x * rotation);
scales[i] = mix(1.0, mix(scale_range.x, scale_range.y, rand[i].y), scale);
offsets[i] = vector2(
mix(offset_range.x, offset_range.y, rand[i].x * offset),
mix(offset_range.x, offset_range.y, rand[i].y * offset));
}

vector sin_r = sin(rotations);
vector cos_r = cos(rotations);

HextileData tile_data;
tile_data.weights = vector(w1, w2, w3);
tile_data.rotations = rotations;

// Transform coordinates for each tile
for (int i = 0; i < 3; i++)
{
vector2 d = coord - ctr[i];
float c = cos_r[i];
float s = sin_r[i];
float sc = scales[i];

tile_data.coords[i] = vector2(
(d.x * c - d.y * s) / sc + ctr[i].x + offsets[i].x,
(d.x * s + d.y * c) / sc + ctr[i].y + offsets[i].y);
}

return tile_data;
}

// Blend 3 normals by blending the gradients
// Morten S. Mikkelsen, Surface Gradient-Based Bump Mapping Framework, Journal of
// Computer Graphics Techniques (JCGT), vol. 9, no. 3, 60-90, 2020
// http://jcgt.org/published/0009/03/04/
vector mx_normals_to_gradient(vector N, vector Np)
{
float d = dot(N, Np);
vector g = (d * N - Np) / max(1e-8, abs(d));
return g;
}

vector mx_gradient_blend_3_normals(vector N, vector N1, float N1_weight, vector N2, float N2_weight, vector N3, float N3_weight)
{
float w1 = clamp(N1_weight, 0.0, 1.0);
float w2 = clamp(N2_weight, 0.0, 1.0);
float w3 = clamp(N3_weight, 0.0, 1.0);

vector g1 = mx_normals_to_gradient(N, N1);
vector g2 = mx_normals_to_gradient(N, N2);
vector g3 = mx_normals_to_gradient(N, N3);

// Blend
vector gg = w1 * g1 + w2 * g2 + w3 * g3;

// Gradient to normal
return normalize(N - gg);
}

// Axis-angle rotation matrix
matrix mx_axis_rotation_matrix(vector axis, float angle)
{
float s = sin(angle);
float c = cos(angle);
float omc = 1.0 - c;
vector a = axis;

return matrix(
a[0]*a[0]*omc + c, a[0]*a[1]*omc - a[2]*s, a[0]*a[2]*omc + a[1]*s, 0.0,
a[1]*a[0]*omc + a[2]*s, a[1]*a[1]*omc + c, a[1]*a[2]*omc - a[0]*s, 0.0,
a[2]*a[0]*omc - a[1]*s, a[2]*a[1]*omc + a[0]*s, a[2]*a[2]*omc + c, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
55 changes: 55 additions & 0 deletions libraries/stdlib/genosl/mx_hextiledimage_color3.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "lib/$fileTransformUv"
#include "lib/mx_hextile.osl"

// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
// http://jcgt.org/published/0011/03/05/
void mx_hextiledimage_color3(
textureresource file,
color default_value,
vector2 texcoord,
vector2 tiling,
float rotation,
vector2 rotationrange,
float scale,
vector2 scalerange,
float offset,
vector2 offsetrange,
float falloff,
float falloffcontrast,
color lumacoeffs,
output color result)
{
if (file.filename == "")
{
result = default_value;
return;
}

vector2 coord = mx_transform_uv(texcoord) * tiling;

HextileData tile_data = mx_hextile_coord(coord, rotation, rotationrange, scale, scalerange, offset, offsetrange);

// Sample textures for each tile
color c[3];
vector cw;

for (int i = 0; i < 3; i++)
{
c[i] = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"missingcolor", default_value,
"swrap", "periodic", "twrap", "periodic"
#if (OSL_VERSION_MAJOR == 1 && OSL_VERSION_MINOR >= 14) || (OSL_VERSION_MAJOR > 1)
, "colorspace", file.colorspace
#endif
);
cw[i] = dot(vector(c[i]), vector(lumacoeffs));
}

// Luminance-based blend weights
cw = mix(vector(1.0), cw, vector(falloffcontrast));
vector w = mx_hextile_compute_blend_weights(cw, tile_data.weights, falloff);

// Blend colors
result = w[0] * c[0] + w[1] * c[1] + w[2] * c[2];
}
65 changes: 65 additions & 0 deletions libraries/stdlib/genosl/mx_hextiledimage_color4.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "lib/$fileTransformUv"
#include "lib/mx_hextile.osl"

// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
// http://jcgt.org/published/0011/03/05/
void mx_hextiledimage_color4(
textureresource file,
color4 default_value,
vector2 texcoord,
vector2 tiling,
float rotation,
vector2 rotationrange,
float scale,
vector2 scalerange,
float offset,
vector2 offsetrange,
float falloff,
float falloffcontrast,
color lumacoeffs,
output color4 result)
{
if (file.filename == "")
{
result = default_value;
return;
}

vector2 coord = mx_transform_uv(texcoord) * tiling;

HextileData tile_data = mx_hextile_coord(coord, rotation, rotationrange, scale, scalerange, offset, offsetrange);

// Sample textures for each tile
color c[3];
float alpha[3];
vector cw;

for (int i = 0; i < 3; i++)
{
c[i] = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"alpha", alpha[i],
"missingcolor", default_value.rgb, "missingalpha", default_value.a,
"swrap", "periodic", "twrap", "periodic"
#if (OSL_VERSION_MAJOR == 1 && OSL_VERSION_MINOR >= 14) || (OSL_VERSION_MAJOR > 1)
, "colorspace", file.colorspace
#endif
);
cw[i] = dot(vector(c[i]), vector(lumacoeffs));
}

// Luminance-based blend weights
cw = mix(vector(1.0), cw, vector(falloffcontrast));
vector w = mx_hextile_compute_blend_weights(cw, tile_data.weights, falloff);

// Alpha (average with optional falloff adjustment)
float a = (alpha[0] + alpha[1] + alpha[2]) / 3.0;
if (falloff != 0.5)
{
a = mx_schlick_gain(a, falloff);
}

// Blend colors
result.rgb = w[0] * c[0] + w[1] * c[1] + w[2] * c[2];
result.a = a;
}
68 changes: 68 additions & 0 deletions libraries/stdlib/genosl/mx_hextilednormalmap_vector3.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include "lib/$fileTransformUv"
#include "lib/mx_hextile.osl"

// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
// http://jcgt.org/published/0011/03/05/
void mx_hextilednormalmap_vector3(
textureresource file,
vector default_value,
vector2 texcoord,
vector2 tiling,
float rotation,
vector2 rotationrange,
float scale,
vector2 scalerange,
float offset,
vector2 offsetrange,
float falloff,
float strength,
int flip_g,
vector N,
vector T,
vector B,
output vector result)
{
if (file.filename == "")
{
result = N;
return;
}

vector2 coord = mx_transform_uv(texcoord) * tiling;

HextileData tile_data = mx_hextile_coord(coord, rotation, rotationrange, scale, scalerange, offset, offsetrange);

// Process each tile: sample, decode, transform, and compute normals
vector tile_normals[3];

for (int i = 0; i < 3; i++)
{
// Sample normal map for this tile
color nm_raw = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"missingcolor", color(default_value),
"swrap", "periodic", "twrap", "periodic");

// Convert to vector and decode normal map
vector nm = vector(nm_raw);
if (flip_g)
nm[1] = 1.0 - nm[1];
nm = 2.0 * nm - 1.0;

// Rotate tangent frame for this tile
matrix tangent_rot_mat = mx_axis_rotation_matrix(N, -tile_data.rotations[i]);
vector Ti = transform(tangent_rot_mat, T) * strength;
vector Bi = transform(tangent_rot_mat, B) * strength;

// The OSL backend uses dPdu and dPdv for tangents and bitangents, but these vectors are not
// guaranteed to be orthonormal. Orthogonalize the tangent frame using Gram-Schmidt.
vector Tn = normalize(Ti - dot(Ti, N) * N);
vector Bn = normalize(Bi - dot(Bi, N) * N - dot(Bi, Tn) * Tn);

tile_normals[i] = normalize(Tn * nm[0] + Bn * nm[1] + N * nm[2]);
}

// Blend weights and normals using gradient blending
vector w = mx_hextile_compute_blend_weights(vector(1.0), tile_data.weights, falloff);
result = mx_gradient_blend_3_normals(N, tile_normals[0], w[0], tile_normals[1], w[1], tile_normals[2], w[2]);
}
13 changes: 13 additions & 0 deletions libraries/stdlib/genosl/stdlib_genosl_impl.mtlx
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,25 @@
<input name="default" type="vector4" implname="default_value" />
</implementation>

<!-- <hextiledimage> -->
<implementation name="IM_hextiledimage_color3_genosl" nodedef="ND_hextiledimage_color3" file="mx_hextiledimage_color3.osl" function="mx_hextiledimage_color3" target="genosl">
<input name="default" type="color3" implname="default_value" />
</implementation>
<implementation name="IM_hextiledimage_color4_genosl" nodedef="ND_hextiledimage_color4" file="mx_hextiledimage_color4.osl" function="mx_hextiledimage_color4" target="genosl">
<input name="default" type="color4" implname="default_value" />
</implementation>

<!-- <triplanarprojection> -->

<!-- <normalmap> -->
<implementation name="IM_normalmap_float_genosl" nodedef="ND_normalmap_float" file="mx_normalmap.osl" function="mx_normalmap_float" target="genosl" />
<implementation name="IM_normalmap_vector2_genosl" nodedef="ND_normalmap_vector2" file="mx_normalmap.osl" function="mx_normalmap_vector2" target="genosl" />

<!-- <hextilednormalmap> -->
<implementation name="IM_hextilednormalmap_vector3_genosl" nodedef="ND_hextilednormalmap_vector3" file="mx_hextilednormalmap_vector3.osl" function="mx_hextilednormalmap_vector3" target="genosl">
<input name="default" type="vector3" implname="default_value" />
</implementation>

<!-- ======================================================================== -->
<!-- Procedural nodes -->
<!-- ======================================================================== -->
Expand Down
Loading