Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GLSL hextiling node implementation #2094

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
42 changes: 42 additions & 0 deletions libraries/stdlib/genglsl/lib/mx_geometry.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

// 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/
vec3 mx_normals_to_gradient(vec3 N, vec3 Np)
{
float d = dot(N, Np);
vec3 g = (d * N - Np) / max(M_FLOAT_EPS, abs(d));
return g;
}

vec3 mx_gradient_blend_3_normals(vec3 N, vec3 N1, float N1_weight, vec3 N2, float N2_weight, vec3 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);

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

// blend
vec3 gg = w1 * g1 + w2 * g2 + w3 * g3;

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

// this function should be categorized in mx_math.glsl but it causes build errors in MSL
// so adding here for a workaround
mat3 mx_axis_rotation_matrix(vec3 a, float r)
{
float s = sin(r);
float c = cos(r);
float omc = 1.0 - c;
return mat3(
a.x*a.x*omc + c, a.x*a.y*omc - a.x*s, a.x*a.z*omc + a.y*s,
a.y*a.x*omc + a.z*s, a.y*a.y*omc + c, a.y*a.z*omc - a.x*s,
a.z*a.x*omc - a.y*s, a.z*a.y*omc + a.x*s, a.z*a.z*omc + c
);
}
137 changes: 137 additions & 0 deletions libraries/stdlib/genglsl/lib/mx_hextile.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// https://www.shadertoy.com/view/4djSRW
vec2 mx_hextile_hash(vec2 p)
{
vec3 p3 = fract(vec3(p.x, p.y, p.x) * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, vec3(p3.y, p3.z, p3.x) + 33.33);
return fract((vec2(p3.x, p3.x) + vec2(p3.y, p3.z)) * vec2(p3.z, p3.y));
}

// 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
{
vec2 coord1;
vec2 coord2;
vec2 coord3;
vec3 weights;
float rot_radian1;
float rot_radian2;
float rot_radian3;
vec2 ddx1;
vec2 ddx2;
vec2 ddx3;
vec2 ddy1;
vec2 ddy2;
vec2 ddy3;
};

// 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(
vec2 coord,
float rotation,
vec2 rotation_range,
float scale,
vec2 scale_range,
float offset,
vec2 offset_range)
{
float sqrt3_2 = sqrt(3.0) * 2.0;

// scale coord to maintain the original fit
vec2 st = coord * sqrt3_2;

// skew input space into simplex triangle grid
// (1, 0, -tan(30), 2*tan(30))
mat2 to_skewed = mat2(1.0, 0.0, -0.57735027, 1.15470054);
vec2 st_skewed = to_skewed * st;

// barycentric weights
vec2 st_frac = fract(st_skewed);
vec3 temp = vec3(st_frac.x, st_frac.y, 0.0);
temp.z = 1.0 - temp.x - temp.y;

float s = step(0.0, -temp.z);
float s2 = 2.0 * s - 1.0;

float w1 = -temp.z * s2;
float w2 = s - temp.y * s2;
float w3 = s - temp.x * s2;

// vertex IDs
ivec2 base_id = ivec2(floor(st_skewed));
int si = int(s);
ivec2 id1 = base_id + ivec2(si, si);
ivec2 id2 = base_id + ivec2(si, 1 - si);
ivec2 id3 = base_id + ivec2(1 - si, si);

// tile center
mat2 inv_skewed = mat2(1.0, 0.0, 0.5, 1.0 / 1.15470054);
vec2 ctr1 = inv_skewed * vec2(id1) / vec2(sqrt3_2);
vec2 ctr2 = inv_skewed * vec2(id2) / vec2(sqrt3_2);
vec2 ctr3 = inv_skewed * vec2(id3) / vec2(sqrt3_2);

// reuse hash for performance
vec2 seed_offset = vec2(0.12345); // to avoid some zeros
vec2 rand1 = mx_hextile_hash(vec2(id1) + seed_offset);
vec2 rand2 = mx_hextile_hash(vec2(id2) + seed_offset);
vec2 rand3 = mx_hextile_hash(vec2(id3) + seed_offset);

// randomized rotation matrix
vec2 rr = mx_radians(rotation_range);
float rv1 = mix(rr.x, rr.y, rand1.x * rotation);
float rv2 = mix(rr.x, rr.y, rand2.x * rotation);
float rv3 = mix(rr.x, rr.y, rand3.x * rotation);
float sin_r1 = sin(rv1);
float sin_r2 = sin(rv2);
float sin_r3 = sin(rv3);
float cos_r1 = cos(rv1);
float cos_r2 = cos(rv2);
float cos_r3 = cos(rv3);
mat2 rm1 = mat2(cos_r1, -sin_r1, sin_r1, cos_r1);
mat2 rm2 = mat2(cos_r2, -sin_r2, sin_r2, cos_r2);
mat2 rm3 = mat2(cos_r3, -sin_r3, sin_r3, cos_r3);

// randomized scale
vec2 sr = scale_range;
vec2 scale1 = vec2(mix(1.0, mix(sr.x, sr.y, rand1.y), scale));
vec2 scale2 = vec2(mix(1.0, mix(sr.x, sr.y, rand2.y), scale));
vec2 scale3 = vec2(mix(1.0, mix(sr.x, sr.y, rand3.y), scale));

// randomized offset
vec2 offset1 = mix(vec2(offset_range.x), vec2(offset_range.y), rand1 * offset);
vec2 offset2 = mix(vec2(offset_range.x), vec2(offset_range.y), rand2 * offset);
vec2 offset3 = mix(vec2(offset_range.x), vec2(offset_range.y), rand3 * offset);

HextileData tile_data;
tile_data.weights = vec3(w1, w2, w3);
tile_data.rot_radian1 = rv1;
tile_data.rot_radian2 = rv2;
tile_data.rot_radian3 = rv3;

// get coord
tile_data.coord1 = ((coord - ctr1) * rm1 / scale1) + ctr1 + offset1;
tile_data.coord2 = ((coord - ctr2) * rm2 / scale2) + ctr2 + offset2;
tile_data.coord3 = ((coord - ctr3) * rm3 / scale3) + ctr3 + offset3;

// derivatives
vec2 ddx = dFdx(coord);
vec2 ddy = dFdy(coord);
tile_data.ddx1 = ddx * rm1 / scale1;
tile_data.ddx2 = ddx * rm2 / scale2;
tile_data.ddx3 = ddx * rm3 / scale3;
tile_data.ddy1 = ddy * rm1 / scale1;
tile_data.ddy2 = ddy * rm2 / scale2;
tile_data.ddy3 = ddy * rm3 / scale3;

return tile_data;
}
102 changes: 102 additions & 0 deletions libraries/stdlib/genglsl/mx_hextiledimage.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include "lib/$fileTransformUv"
#include "lib/mx_hextile.glsl"

// 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(
sampler2D tex_sampler,
vec3 default_value,
vec2 tex_coord,
vec2 tiling,
float rotation,
vec2 rotation_range,
float scale,
vec2 scale_range,
float offset,
vec2 offset_range,
float falloff,
float falloff_contrast,
vec3 lumacoeffs,
out vec3 result
)
{
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));

HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);

vec3 c1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1).rgb;
vec3 c2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2).rgb;
vec3 c3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3).rgb;

// luminance as weights
vec3 cw = vec3(dot(c1, lumacoeffs), dot(c2, lumacoeffs), dot(c3, lumacoeffs));
cw = mix(vec3(1.0), cw, vec3(falloff_contrast));

// blend weights
vec3 w = cw * pow(tile_data.weights, vec3(7.0));
w /= (w.x + w.y + w.z);

// apply s-curve gain
if (falloff != 0.5)
{
w.x = mx_schlick_gain(w.x, falloff);
w.y = mx_schlick_gain(w.y, falloff);
w.z = mx_schlick_gain(w.z, falloff);
w /= (w.x + w.y + w.z);
}

// blend
result = vec3(w.x * c1 + w.y * c2 + w.z * c3);
}

void mx_hextiledimage_color4(
sampler2D tex_sampler,
vec4 default_value,
vec2 tex_coord,
vec2 tiling,
float rotation,
vec2 rotation_range,
float scale,
vec2 scale_range,
float offset,
vec2 offset_range,
float falloff,
float falloff_contrast,
vec3 lumacoeffs,
out vec4 result
)
{
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));

HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);

vec4 c1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1);
vec4 c2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2);
vec4 c3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3);

// luminance as weights
vec3 cw = vec3(dot(c1.rgb, lumacoeffs), dot(c2.rgb, lumacoeffs), dot(c3.rgb, lumacoeffs));
cw = mix(vec3(1.0), cw, vec3(falloff_contrast));

// blend weights
vec3 w = cw * pow(tile_data.weights, vec3(7.0));
w /= (w.x + w.y + w.z);

// alpha
float a = (c1.a + c2.a + c3.a) / 3.0;

// apply s-curve gain
if (falloff != 0.5)
{
w.x = mx_schlick_gain(w.x, falloff);
w.y = mx_schlick_gain(w.y, falloff);
w.z = mx_schlick_gain(w.z, falloff);
w /= (w.x + w.y + w.z);
a = mx_schlick_gain(a, falloff);
}

// blend
result.rgb = vec3(w.x * c1 + w.y * c2 + w.z * c3);
result.a = a;
}
71 changes: 71 additions & 0 deletions libraries/stdlib/genglsl/mx_hextilednormalmap.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "lib/$fileTransformUv"
#include "lib/mx_hextile.glsl"
#include "lib/mx_geometry.glsl"

// 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(
sampler2D tex_sampler,
vec3 default_value,
vec2 tex_coord,
vec2 tiling,
float rotation,
vec2 rotation_range,
float scale,
vec2 scale_range,
float offset,
vec2 offset_range,
float falloff,
float strength,
bool flip_g,
vec3 N,
vec3 T,
vec3 B,
out vec3 result
)
{
vec2 coord = mx_transform_uv(tex_coord, tiling, vec2(0.0));

HextileData tile_data = mx_hextile_coord(coord, rotation, rotation_range, scale, scale_range, offset, offset_range);

vec3 nm1 = textureGrad(tex_sampler, tile_data.coord1, tile_data.ddx1, tile_data.ddy1).xyz;
vec3 nm2 = textureGrad(tex_sampler, tile_data.coord2, tile_data.ddx2, tile_data.ddy2).xyz;
vec3 nm3 = textureGrad(tex_sampler, tile_data.coord3, tile_data.ddx3, tile_data.ddy3).xyz;
nm1.y = flip_g ? 1.0 - nm1.y : nm1.y;
nm2.y = flip_g ? 1.0 - nm2.y : nm2.y;
nm3.y = flip_g ? 1.0 - nm3.y : nm3.y;

// normalmap to shading normal
nm1 = 2.0 * nm1 - 1.0;
nm2 = 2.0 * nm2 - 1.0;
nm3 = 2.0 * nm3 - 1.0;
mat3 tangent_rot_mat1 = mx_axis_rotation_matrix(N, -tile_data.rot_radian1);
mat3 tangent_rot_mat2 = mx_axis_rotation_matrix(N, -tile_data.rot_radian2);
mat3 tangent_rot_mat3 = mx_axis_rotation_matrix(N, -tile_data.rot_radian3);
vec3 T1 = tangent_rot_mat1 * T * strength;
vec3 T2 = tangent_rot_mat2 * T * strength;
vec3 T3 = tangent_rot_mat3 * T * strength;
vec3 B1 = tangent_rot_mat1 * B * strength;
vec3 B2 = tangent_rot_mat2 * B * strength;
vec3 B3 = tangent_rot_mat3 * B * strength;
vec3 N1 = normalize(T1 * nm1.x + B1 * nm1.y + N * nm1.z);
vec3 N2 = normalize(T2 * nm2.x + B2 * nm2.y + N * nm2.z);
vec3 N3 = normalize(T3 * nm3.x + B3 * nm3.y + N * nm3.z);

// blend weights
vec3 w = pow(tile_data.weights, vec3(7.0));
w /= (w.x + w.y + w.z);

// apply s-curve gain
if (falloff != 0.5)
{
w.x = mx_schlick_gain(w.x, falloff);
w.y = mx_schlick_gain(w.y, falloff);
w.z = mx_schlick_gain(w.z, falloff);
w /= (w.x + w.y + w.z);
}

// blend
result = mx_gradient_blend_3_normals(N, N1, w.x, N2, w.y, N3, w.z);
}
13 changes: 13 additions & 0 deletions libraries/stdlib/genglsl/stdlib_genglsl_impl.mtlx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,23 @@
<input name="default" type="vector4" implname="default_value" />
</implementation>

<!-- <hextiledimage> -->
<implementation name="IM_hextiledimage_color3_genglsl" nodedef="ND_hextiledimage_color3" file="mx_hextiledimage.glsl" function="mx_hextiledimage_color3" target="genglsl">
<input name="default" type="color3" implname="default_value" />
</implementation>
<implementation name="IM_hextiledimage_color4_genglsl" nodedef="ND_hextiledimage_color4" file="mx_hextiledimage.glsl" function="mx_hextiledimage_color4" target="genglsl">
<input name="default" type="color4" implname="default_value" />
</implementation>

<!-- <normalmap> -->
<implementation name="IM_normalmap_float_genglsl" nodedef="ND_normalmap_float" file="mx_normalmap.glsl" function="mx_normalmap_float" target="genglsl" />
<implementation name="IM_normalmap_vector2_genglsl" nodedef="ND_normalmap_vector2" file="mx_normalmap.glsl" function="mx_normalmap_vector2" target="genglsl" />

<!-- <hextilednormalmap> -->
<implementation name="IM_hextilednormalmap_vector3_genglsl" nodedef="ND_hextilednormalmap_vector3" file="mx_hextilednormalmap.glsl" function="mx_hextilednormalmap_vector3" target="genglsl">
<input name="default" type="vector3" implname="default_value" />
</implementation>

<!-- ======================================================================== -->
<!-- Procedural nodes -->
<!-- ======================================================================== -->
Expand Down
5 changes: 5 additions & 0 deletions libraries/stdlib/genmsl/lib/mx_math.metal
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,8 @@ float mx_radians(float degree)
{
return (degree * M_PI_F / 180.0f);
}

vec2 mx_radians(vec2 degree)
{
return (degree * M_PI_F / 180.0f);
}
Loading