Skip to content

Commit d1c46f7

Browse files
Add GLSL hextiling node implementation (#2094)
This PR adds GLSL implementation for hextiling nodes proposed by #2093. Nodes to add: - `hextiledimage` (`color3` and `color4`) - `hextilednormalmap` (`vector3`)
1 parent fae75e7 commit d1c46f7

16 files changed

Lines changed: 473 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Blend 3 normals by blending the gradients
2+
// Morten S. Mikkelsen, Surface Gradient–Based Bump Mapping Framework, Journal of
3+
// Computer Graphics Techniques (JCGT), vol. 9, no. 3, 60–90, 2020
4+
// http://jcgt.org/published/0009/03/04/
5+
vec3 mx_normals_to_gradient(vec3 N, vec3 Np)
6+
{
7+
float d = dot(N, Np);
8+
vec3 g = (d * N - Np) / max(M_FLOAT_EPS, abs(d));
9+
return g;
10+
}
11+
12+
vec3 mx_gradient_blend_3_normals(vec3 N, vec3 N1, float N1_weight, vec3 N2, float N2_weight, vec3 N3, float N3_weight)
13+
{
14+
float w1 = clamp(N1_weight, 0.0, 1.0);
15+
float w2 = clamp(N2_weight, 0.0, 1.0);
16+
float w3 = clamp(N3_weight, 0.0, 1.0);
17+
18+
vec3 g1 = mx_normals_to_gradient(N, N1);
19+
vec3 g2 = mx_normals_to_gradient(N, N2);
20+
vec3 g3 = mx_normals_to_gradient(N, N3);
21+
22+
// blend
23+
vec3 gg = w1 * g1 + w2 * g2 + w3 * g3;
24+
25+
// gradient to normal
26+
return normalize(N - gg);
27+
}
28+
29+
// This function should be categorized in mx_math.glsl but it causes build errors in MSL
30+
// so adding here for a workaround
31+
mat3 mx_axis_rotation_matrix(vec3 a, float r)
32+
{
33+
float s = sin(r);
34+
float c = cos(r);
35+
float omc = 1.0 - c;
36+
return mat3(
37+
a.x*a.x*omc + c, a.x*a.y*omc - a.x*s, a.x*a.z*omc + a.y*s,
38+
a.y*a.x*omc + a.z*s, a.y*a.y*omc + c, a.y*a.z*omc - a.x*s,
39+
a.z*a.x*omc - a.y*s, a.z*a.y*omc + a.x*s, a.z*a.z*omc + c
40+
);
41+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// https://www.shadertoy.com/view/4djSRW
2+
vec2 mx_hextile_hash(vec2 p)
3+
{
4+
vec3 p3 = fract(vec3(p.x, p.y, p.x) * vec3(0.1031, 0.1030, 0.0973));
5+
p3 += dot(p3, vec3(p3.y, p3.z, p3.x) + 33.33);
6+
return fract((vec2(p3.x, p3.x) + vec2(p3.y, p3.z)) * vec2(p3.z, p3.y));
7+
}
8+
9+
// Christophe Schlick. “Fast Alternatives to Perlin’s Bias and Gain Functions”.
10+
// In Graphics Gems IV, Morgan Kaufmann, 1994, pages 401–403.
11+
// https://dept-info.labri.fr/~schlick/DOC/gem2.html
12+
float mx_schlick_gain(float x, float r)
13+
{
14+
float rr = clamp(r, 0.001, 0.999); // to avoid glitch
15+
float a = (1.0 / rr - 2.0) * (1.0 - 2.0 * x);
16+
return (x < 0.5) ? x / (a + 1.0) : (a - x) / (a - 1.0);
17+
}
18+
19+
struct HextileData
20+
{
21+
vec2 coord1;
22+
vec2 coord2;
23+
vec2 coord3;
24+
vec3 weights;
25+
float rot_radian1;
26+
float rot_radian2;
27+
float rot_radian3;
28+
vec2 ddx1;
29+
vec2 ddx2;
30+
vec2 ddx3;
31+
vec2 ddy1;
32+
vec2 ddy2;
33+
vec2 ddy3;
34+
};
35+
36+
// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
37+
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
38+
// http://jcgt.org/published/0011/03/05/
39+
HextileData mx_hextile_coord(
40+
vec2 coord,
41+
float rotation,
42+
vec2 rotation_range,
43+
float scale,
44+
vec2 scale_range,
45+
float offset,
46+
vec2 offset_range)
47+
{
48+
float sqrt3_2 = sqrt(3.0) * 2.0;
49+
50+
// scale coord to maintain the original fit
51+
vec2 st = coord * sqrt3_2;
52+
53+
// skew input space into simplex triangle grid
54+
// (1, 0, -tan(30), 2*tan(30))
55+
mat2 to_skewed = mat2(1.0, 0.0, -0.57735027, 1.15470054);
56+
vec2 st_skewed = to_skewed * st;
57+
58+
// barycentric weights
59+
vec2 st_frac = fract(st_skewed);
60+
vec3 temp = vec3(st_frac.x, st_frac.y, 0.0);
61+
temp.z = 1.0 - temp.x - temp.y;
62+
63+
float s = step(0.0, -temp.z);
64+
float s2 = 2.0 * s - 1.0;
65+
66+
float w1 = -temp.z * s2;
67+
float w2 = s - temp.y * s2;
68+
float w3 = s - temp.x * s2;
69+
70+
// vertex IDs
71+
ivec2 base_id = ivec2(floor(st_skewed));
72+
int si = int(s);
73+
ivec2 id1 = base_id + ivec2(si, si);
74+
ivec2 id2 = base_id + ivec2(si, 1 - si);
75+
ivec2 id3 = base_id + ivec2(1 - si, si);
76+
77+
// tile center
78+
mat2 inv_skewed = mat2(1.0, 0.0, 0.5, 1.0 / 1.15470054);
79+
vec2 ctr1 = inv_skewed * vec2(id1) / vec2(sqrt3_2);
80+
vec2 ctr2 = inv_skewed * vec2(id2) / vec2(sqrt3_2);
81+
vec2 ctr3 = inv_skewed * vec2(id3) / vec2(sqrt3_2);
82+
83+
// reuse hash for performance
84+
vec2 seed_offset = vec2(0.12345); // to avoid some zeros
85+
vec2 rand1 = mx_hextile_hash(vec2(id1) + seed_offset);
86+
vec2 rand2 = mx_hextile_hash(vec2(id2) + seed_offset);
87+
vec2 rand3 = mx_hextile_hash(vec2(id3) + seed_offset);
88+
89+
// randomized rotation matrix
90+
vec2 rr = mx_radians(rotation_range);
91+
float rv1 = mix(rr.x, rr.y, rand1.x * rotation);
92+
float rv2 = mix(rr.x, rr.y, rand2.x * rotation);
93+
float rv3 = mix(rr.x, rr.y, rand3.x * rotation);
94+
float sin_r1 = sin(rv1);
95+
float sin_r2 = sin(rv2);
96+
float sin_r3 = sin(rv3);
97+
float cos_r1 = cos(rv1);
98+
float cos_r2 = cos(rv2);
99+
float cos_r3 = cos(rv3);
100+
mat2 rm1 = mat2(cos_r1, -sin_r1, sin_r1, cos_r1);
101+
mat2 rm2 = mat2(cos_r2, -sin_r2, sin_r2, cos_r2);
102+
mat2 rm3 = mat2(cos_r3, -sin_r3, sin_r3, cos_r3);
103+
104+
// randomized scale
105+
vec2 sr = scale_range;
106+
vec2 scale1 = vec2(mix(1.0, mix(sr.x, sr.y, rand1.y), scale));
107+
vec2 scale2 = vec2(mix(1.0, mix(sr.x, sr.y, rand2.y), scale));
108+
vec2 scale3 = vec2(mix(1.0, mix(sr.x, sr.y, rand3.y), scale));
109+
110+
// randomized offset
111+
vec2 offset1 = mix(vec2(offset_range.x), vec2(offset_range.y), rand1 * offset);
112+
vec2 offset2 = mix(vec2(offset_range.x), vec2(offset_range.y), rand2 * offset);
113+
vec2 offset3 = mix(vec2(offset_range.x), vec2(offset_range.y), rand3 * offset);
114+
115+
HextileData tile_data;
116+
tile_data.weights = vec3(w1, w2, w3);
117+
tile_data.rot_radian1 = rv1;
118+
tile_data.rot_radian2 = rv2;
119+
tile_data.rot_radian3 = rv3;
120+
121+
// get coord
122+
tile_data.coord1 = ((coord - ctr1) * rm1 / scale1) + ctr1 + offset1;
123+
tile_data.coord2 = ((coord - ctr2) * rm2 / scale2) + ctr2 + offset2;
124+
tile_data.coord3 = ((coord - ctr3) * rm3 / scale3) + ctr3 + offset3;
125+
126+
// derivatives
127+
vec2 ddx = dFdx(coord);
128+
vec2 ddy = dFdy(coord);
129+
tile_data.ddx1 = ddx * rm1 / scale1;
130+
tile_data.ddx2 = ddx * rm2 / scale2;
131+
tile_data.ddx3 = ddx * rm3 / scale3;
132+
tile_data.ddy1 = ddy * rm1 / scale1;
133+
tile_data.ddy2 = ddy * rm2 / scale2;
134+
tile_data.ddy3 = ddy * rm3 / scale3;
135+
136+
return tile_data;
137+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#include "lib/$fileTransformUv"
2+
#include "lib/mx_hextile.glsl"
3+
4+
// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
5+
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
6+
// http://jcgt.org/published/0011/03/05/
7+
void mx_hextiledimage_color3(
8+
sampler2D tex_sampler,
9+
vec3 default_value,
10+
vec2 tex_coord,
11+
vec2 tiling,
12+
float rotation,
13+
vec2 rotation_range,
14+
float scale,
15+
vec2 scale_range,
16+
float offset,
17+
vec2 offset_range,
18+
float falloff,
19+
float falloff_contrast,
20+
vec3 lumacoeffs,
21+
out vec3 result
22+
)
23+
{
24+
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));
25+
26+
HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);
27+
28+
vec3 c1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1).rgb;
29+
vec3 c2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2).rgb;
30+
vec3 c3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3).rgb;
31+
32+
// luminance as weights
33+
vec3 cw = vec3(dot(c1, lumacoeffs), dot(c2, lumacoeffs), dot(c3, lumacoeffs));
34+
cw = mix(vec3(1.0), cw, vec3(falloff_contrast));
35+
36+
// blend weights
37+
vec3 w = cw * pow(tile_data.weights, vec3(7.0));
38+
w /= (w.x + w.y + w.z);
39+
40+
// apply s-curve gain
41+
if (falloff != 0.5)
42+
{
43+
w.x = mx_schlick_gain(w.x, falloff);
44+
w.y = mx_schlick_gain(w.y, falloff);
45+
w.z = mx_schlick_gain(w.z, falloff);
46+
w /= (w.x + w.y + w.z);
47+
}
48+
49+
// blend
50+
result = vec3(w.x * c1 + w.y * c2 + w.z * c3);
51+
}
52+
53+
void mx_hextiledimage_color4(
54+
sampler2D tex_sampler,
55+
vec4 default_value,
56+
vec2 tex_coord,
57+
vec2 tiling,
58+
float rotation,
59+
vec2 rotation_range,
60+
float scale,
61+
vec2 scale_range,
62+
float offset,
63+
vec2 offset_range,
64+
float falloff,
65+
float falloff_contrast,
66+
vec3 lumacoeffs,
67+
out vec4 result
68+
)
69+
{
70+
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));
71+
72+
HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);
73+
74+
vec4 c1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1);
75+
vec4 c2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2);
76+
vec4 c3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3);
77+
78+
// luminance as weights
79+
vec3 cw = vec3(dot(c1.rgb, lumacoeffs), dot(c2.rgb, lumacoeffs), dot(c3.rgb, lumacoeffs));
80+
cw = mix(vec3(1.0), cw, vec3(falloff_contrast));
81+
82+
// blend weights
83+
vec3 w = cw * pow(tile_data.weights, vec3(7.0));
84+
w /= (w.x + w.y + w.z);
85+
86+
// alpha
87+
float a = (c1.a + c2.a + c3.a) / 3.0;
88+
89+
// apply s-curve gain
90+
if (falloff != 0.5)
91+
{
92+
w.x = mx_schlick_gain(w.x, falloff);
93+
w.y = mx_schlick_gain(w.y, falloff);
94+
w.z = mx_schlick_gain(w.z, falloff);
95+
w /= (w.x + w.y + w.z);
96+
a = mx_schlick_gain(a, falloff);
97+
}
98+
99+
// blend
100+
result.rgb = vec3(w.x * c1 + w.y * c2 + w.z * c3);
101+
result.a = a;
102+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include "lib/$fileTransformUv"
2+
#include "lib/mx_hextile.glsl"
3+
#include "lib/mx_geometry.glsl"
4+
5+
// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics
6+
// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022
7+
// http://jcgt.org/published/0011/03/05/
8+
void mx_hextilednormalmap_vector3(
9+
sampler2D tex_sampler,
10+
vec3 default_value,
11+
vec2 tex_coord,
12+
vec2 tiling,
13+
float rotation,
14+
vec2 rotation_range,
15+
float scale,
16+
vec2 scale_range,
17+
float offset,
18+
vec2 offset_range,
19+
float falloff,
20+
float strength,
21+
bool flip_g,
22+
vec3 N,
23+
vec3 T,
24+
vec3 B,
25+
out vec3 result
26+
)
27+
{
28+
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));
29+
30+
HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);
31+
32+
vec3 nm1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1).xyz;
33+
vec3 nm2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2).xyz;
34+
vec3 nm3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3).xyz;
35+
nm1.y = flip_g ? 1.0 - nm1.y : nm1.y;
36+
nm2.y = flip_g ? 1.0 - nm2.y : nm2.y;
37+
nm3.y = flip_g ? 1.0 - nm3.y : nm3.y;
38+
39+
// normalmap to shading normal
40+
nm1 = 2.0 * nm1 - 1.0;
41+
nm2 = 2.0 * nm2 - 1.0;
42+
nm3 = 2.0 * nm3 - 1.0;
43+
mat3 tangent_rot_mat1 = mx_axis_rotation_matrix(N, -tile_data.rot_radian1);
44+
mat3 tangent_rot_mat2 = mx_axis_rotation_matrix(N, -tile_data.rot_radian2);
45+
mat3 tangent_rot_mat3 = mx_axis_rotation_matrix(N, -tile_data.rot_radian3);
46+
vec3 T1 = tangent_rot_mat1 * T * strength;
47+
vec3 T2 = tangent_rot_mat2 * T * strength;
48+
vec3 T3 = tangent_rot_mat3 * T * strength;
49+
vec3 B1 = tangent_rot_mat1 * B * strength;
50+
vec3 B2 = tangent_rot_mat2 * B * strength;
51+
vec3 B3 = tangent_rot_mat3 * B * strength;
52+
vec3 N1 = normalize(T1 * nm1.x + B1 * nm1.y + N * nm1.z);
53+
vec3 N2 = normalize(T2 * nm2.x + B2 * nm2.y + N * nm2.z);
54+
vec3 N3 = normalize(T3 * nm3.x + B3 * nm3.y + N * nm3.z);
55+
56+
// blend weights
57+
vec3 w = pow(tile_data.weights, vec3(7.0));
58+
w /= (w.x + w.y + w.z);
59+
60+
// apply s-curve gain
61+
if (falloff != 0.5)
62+
{
63+
w.x = mx_schlick_gain(w.x, falloff);
64+
w.y = mx_schlick_gain(w.y, falloff);
65+
w.z = mx_schlick_gain(w.z, falloff);
66+
w /= (w.x + w.y + w.z);
67+
}
68+
69+
// blend
70+
result = mx_gradient_blend_3_normals(N, N1, w.x, N2, w.y, N3, w.z);
71+
}

libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,23 @@
4141
<input name="default" type="vector4" implname="default_value" />
4242
</implementation>
4343

44+
<!-- <hextiledimage> -->
45+
<implementation name="IM_hextiledimage_color3_genglsl" nodedef="ND_hextiledimage_color3" file="mx_hextiledimage.glsl" function="mx_hextiledimage_color3" target="genglsl">
46+
<input name="default" type="color3" implname="default_value" />
47+
</implementation>
48+
<implementation name="IM_hextiledimage_color4_genglsl" nodedef="ND_hextiledimage_color4" file="mx_hextiledimage.glsl" function="mx_hextiledimage_color4" target="genglsl">
49+
<input name="default" type="color4" implname="default_value" />
50+
</implementation>
51+
4452
<!-- <normalmap> -->
4553
<implementation name="IM_normalmap_float_genglsl" nodedef="ND_normalmap_float" file="mx_normalmap.glsl" function="mx_normalmap_float" target="genglsl" />
4654
<implementation name="IM_normalmap_vector2_genglsl" nodedef="ND_normalmap_vector2" file="mx_normalmap.glsl" function="mx_normalmap_vector2" target="genglsl" />
4755

56+
<!-- <hextilednormalmap> -->
57+
<implementation name="IM_hextilednormalmap_vector3_genglsl" nodedef="ND_hextilednormalmap_vector3" file="mx_hextilednormalmap.glsl" function="mx_hextilednormalmap_vector3" target="genglsl">
58+
<input name="default" type="vector3" implname="default_value" />
59+
</implementation>
60+
4861
<!-- ======================================================================== -->
4962
<!-- Procedural nodes -->
5063
<!-- ======================================================================== -->

libraries/stdlib/genmsl/lib/mx_math.metal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,8 @@ float mx_radians(float degree)
127127
{
128128
return (degree * M_PI_F / 180.0f);
129129
}
130+
131+
vec2 mx_radians(vec2 degree)
132+
{
133+
return (degree * M_PI_F / 180.0f);
134+
}

libraries/stdlib/genmsl/lib/mx_texture.metal

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ float4 textureLod(MetalTexture mtlTex, float2 uv, float lod)
1919
return mtlTex.tex.sample(mtlTex.s, uv, level(lod));
2020
}
2121

22+
float4 textureGrad(MetalTexture mtlTex, float2 uv, float2 dx, float2 dy)
23+
{
24+
return mtlTex.tex.sample(mtlTex.s, uv, gradient2d(dx, dy));
25+
}
26+
2227
int2 textureSize(MetalTexture mtlTex, int mipLevel)
2328
{
2429
return int2(mtlTex.tex.get_width(), mtlTex.tex.get_height());

0 commit comments

Comments
 (0)