Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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.
189 changes: 189 additions & 0 deletions libraries/stdlib/genosl/lib/mx_hextile.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// 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;
vector2 ddx[3];
vector2 ddy[3];
};

// 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 and derivatives for each tile
vector2 ddx_coord = vector2(Dx(coord.x), Dx(coord.y));
vector2 ddy_coord = vector2(Dy(coord.x), Dy(coord.y));

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);

tile_data.ddx[i] = vector2(
(ddx_coord.x * c - ddx_coord.y * s) / sc,
(ddx_coord.x * s + ddx_coord.y * c) / sc);

tile_data.ddy[i] = vector2(
(ddy_coord.x * c - ddy_coord.y * s) / sc,
(ddy_coord.x * s + ddy_coord.y * c) / sc);
}

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
);
}
57 changes: 57 additions & 0 deletions libraries/stdlib/genosl/mx_hextiledimage_color3.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#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 with explicit derivatives
color c[3];
vector cw;

for (int i = 0; i < 3; i++)
{
float filter_width = length(tile_data.ddx[i]) + length(tile_data.ddy[i]);
c[i] = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"swidth", filter_width, "twidth", filter_width,
"missingcolor", default_value,
"swrap", "periodic", "twrap", "periodic"
#if OSL_VERSION_MAJOR >= 1 && OSL_VERSION_MINOR >= 14
, "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];
}
67 changes: 67 additions & 0 deletions libraries/stdlib/genosl/mx_hextiledimage_color4.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#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 with explicit derivatives
color c[3];
float alpha[3];
vector cw;

for (int i = 0; i < 3; i++)
{
float filter_width = length(tile_data.ddx[i]) + length(tile_data.ddy[i]);
c[i] = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"alpha", alpha[i],
"swidth", filter_width, "twidth", filter_width,
"missingcolor", default_value.rgb, "missingalpha", default_value.a,
"swrap", "periodic", "twrap", "periodic"
#if OSL_VERSION_MAJOR >= 1 && OSL_VERSION_MINOR >= 14
, "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;
}
70 changes: 70 additions & 0 deletions libraries/stdlib/genosl/mx_hextilednormalmap_vector3.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#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 with explicit derivatives
float filter_width = length(tile_data.ddx[i]) + length(tile_data.ddy[i]);
color nm_raw = texture(file.filename, tile_data.coords[i].x, tile_data.coords[i].y,
"swidth", filter_width, "twidth", filter_width,
"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]);
}
Loading