diff --git a/libraries/README.md b/libraries/README.md index 40842a0e28..a1a868798b 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -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. diff --git a/libraries/stdlib/genosl/lib/mx_hextile.osl b/libraries/stdlib/genosl/lib/mx_hextile.osl new file mode 100644 index 0000000000..ab064f28de --- /dev/null +++ b/libraries/stdlib/genosl/lib/mx_hextile.osl @@ -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 + ); +} diff --git a/libraries/stdlib/genosl/mx_hextiledimage_color3.osl b/libraries/stdlib/genosl/mx_hextiledimage_color3.osl new file mode 100644 index 0000000000..d052f65f36 --- /dev/null +++ b/libraries/stdlib/genosl/mx_hextiledimage_color3.osl @@ -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]; +} diff --git a/libraries/stdlib/genosl/mx_hextiledimage_color4.osl b/libraries/stdlib/genosl/mx_hextiledimage_color4.osl new file mode 100644 index 0000000000..cb273fb3c9 --- /dev/null +++ b/libraries/stdlib/genosl/mx_hextiledimage_color4.osl @@ -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; +} diff --git a/libraries/stdlib/genosl/mx_hextilednormalmap_vector3.osl b/libraries/stdlib/genosl/mx_hextilednormalmap_vector3.osl new file mode 100644 index 0000000000..266287663a --- /dev/null +++ b/libraries/stdlib/genosl/mx_hextilednormalmap_vector3.osl @@ -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]); +} diff --git a/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx b/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx index b909c5e89a..80f21aca74 100644 --- a/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx +++ b/libraries/stdlib/genosl/stdlib_genosl_impl.mtlx @@ -67,12 +67,25 @@ + + + + + + + + + + + + + diff --git a/resources/Materials/TestSuite/pbrlib/surfaceshader/standard_surface_onyx_hextiled.mtlx b/resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled.mtlx similarity index 97% rename from resources/Materials/TestSuite/pbrlib/surfaceshader/standard_surface_onyx_hextiled.mtlx rename to resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled.mtlx index 19b08a27c3..72d7098849 100644 --- a/resources/Materials/TestSuite/pbrlib/surfaceshader/standard_surface_onyx_hextiled.mtlx +++ b/resources/Materials/Examples/StandardSurface/standard_surface_onyx_hextiled.mtlx @@ -1,6 +1,6 @@ - + diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp index c56930d01b..deb9ddd45c 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.cpp @@ -90,10 +90,6 @@ TEST_CASE("GenShader: OSL Implementation Check", "[genosl]") generatorSkipNodeTypes.insert("light"); mx::StringSet generatorSkipNodeDefs; - generatorSkipNodeDefs.insert("ND_hextiledimage_color3"); - generatorSkipNodeDefs.insert("ND_hextiledimage_color4"); - generatorSkipNodeDefs.insert("ND_hextilednormalmap_vector3"); - GenShaderUtil::checkImplementations(context, generatorSkipNodeTypes, generatorSkipNodeDefs); } diff --git a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h index 634abb8b60..cf704da805 100644 --- a/source/MaterialXTest/MaterialXGenOsl/GenOsl.h +++ b/source/MaterialXTest/MaterialXGenOsl/GenOsl.h @@ -45,8 +45,8 @@ class OslShaderGeneratorTester : public GenShaderUtil::ShaderGeneratorTester void addSkipFiles() override { - _skipFiles.insert("standard_surface_onyx_hextiled.mtlx"); - _skipFiles.insert("hextiled.mtlx"); + // No additional files are skipped + ShaderGeneratorTester::addSkipFiles(); } // Ignore light shaders in the document for OSL diff --git a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp index 42ab9aee9c..c8b8d45bbb 100644 --- a/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp +++ b/source/MaterialXTest/MaterialXRenderOsl/RenderOsl.cpp @@ -105,10 +105,8 @@ class OslShaderRenderTester : public RenderUtil::ShaderRenderTester void addSkipFiles() override { - _skipFiles.insert("standard_surface_onyx_hextiled.mtlx"); if (_useOslCmdStr) { - _skipFiles.insert("hextiled.mtlx"); _skipFiles.insert("filename_cm_test.mtlx"); _skipFiles.insert("shader_ops.mtlx"); _skipFiles.insert("chiang_hair_surfaceshader.mtlx");