|
| 1 | +// https://www.shadertoy.com/view/4djSRW |
| 2 | +vector2 mx_hextile_hash(vector2 p) |
| 3 | +{ |
| 4 | + vector pp3 = fmod(vector(p.x, p.y, p.x) * vector(0.1031, 0.1030, 0.0973), 1.0); |
| 5 | + pp3 += dot(pp3, vector(pp3[1], pp3[2], pp3[0]) + 33.33); |
| 6 | + return fmod(vector2(pp3[0] + pp3[1], pp3[0] + pp3[2]) * vector2(pp3[2], pp3[1]), 1.0); |
| 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 | + vector2 coords[3]; |
| 22 | + vector weights; |
| 23 | + vector rotations; |
| 24 | +}; |
| 25 | + |
| 26 | +// Helper function to compute blend weights with optional falloff |
| 27 | +vector mx_hextile_compute_blend_weights(vector luminance_weights, vector tile_weights, float falloff) |
| 28 | +{ |
| 29 | + vector w = luminance_weights * pow(tile_weights, 7.0); |
| 30 | + w /= (w[0] + w[1] + w[2]); |
| 31 | + |
| 32 | + if (falloff != 0.5) |
| 33 | + { |
| 34 | + w[0] = mx_schlick_gain(w[0], falloff); |
| 35 | + w[1] = mx_schlick_gain(w[1], falloff); |
| 36 | + w[2] = mx_schlick_gain(w[2], falloff); |
| 37 | + w /= (w[0] + w[1] + w[2]); |
| 38 | + } |
| 39 | + return w; |
| 40 | +} |
| 41 | + |
| 42 | +// Morten S. Mikkelsen, Practical Real-Time Hex-Tiling, Journal of Computer Graphics |
| 43 | +// Techniques (JCGT), vol. 11, no. 2, 77-94, 2022 |
| 44 | +// http://jcgt.org/published/0011/03/05/ |
| 45 | +HextileData mx_hextile_coord( |
| 46 | + vector2 coord, |
| 47 | + float rotation, |
| 48 | + vector2 rotation_range, |
| 49 | + float scale, |
| 50 | + vector2 scale_range, |
| 51 | + float offset, |
| 52 | + vector2 offset_range) |
| 53 | +{ |
| 54 | + float sqrt3_2 = sqrt(3.0) * 2.0; |
| 55 | + |
| 56 | + // Scale coord to maintain the original fit |
| 57 | + vector2 st = coord * sqrt3_2; |
| 58 | + |
| 59 | + // Skew input space into simplex triangle grid |
| 60 | + // (1, 0, -tan(30), 2*tan(30)) |
| 61 | + vector2 st_skewed = vector2(st.x + st.y * -0.57735027, st.y * 1.15470054); |
| 62 | + |
| 63 | + // Barycentric weights |
| 64 | + vector2 st_frac = fmod(st_skewed, 1.0); |
| 65 | + // Handle negative coordinates properly |
| 66 | + if (st_frac.x < 0.0) st_frac.x += 1.0; |
| 67 | + if (st_frac.y < 0.0) st_frac.y += 1.0; |
| 68 | + |
| 69 | + vector temp = vector(st_frac.x, st_frac.y, 0.0); |
| 70 | + temp[2] = 1.0 - temp[0] - temp[1]; |
| 71 | + |
| 72 | + float s = step(0.0, -temp[2]); |
| 73 | + float s2 = 2.0 * s - 1.0; |
| 74 | + |
| 75 | + float w1 = -temp[2] * s2; |
| 76 | + float w2 = s - temp[1] * s2; |
| 77 | + float w3 = s - temp[0] * s2; |
| 78 | + |
| 79 | + // Vertex IDs |
| 80 | + int base_id_x = int(floor(st_skewed.x)); |
| 81 | + int base_id_y = int(floor(st_skewed.y)); |
| 82 | + int si = int(s); |
| 83 | + int id1_x = base_id_x + si; |
| 84 | + int id1_y = base_id_y + si; |
| 85 | + int id2_x = base_id_x + si; |
| 86 | + int id2_y = base_id_y + 1 - si; |
| 87 | + int id3_x = base_id_x + 1 - si; |
| 88 | + int id3_y = base_id_y + si; |
| 89 | + |
| 90 | + // Vertex IDs as array for looping |
| 91 | + int id_x[3] = { id1_x, id2_x, id3_x }; |
| 92 | + int id_y[3] = { id1_y, id2_y, id3_y }; |
| 93 | + |
| 94 | + // Tile centers, random values, and offsets |
| 95 | + vector2 ctr[3], rand[3], offsets[3]; |
| 96 | + vector2 seed_offset = vector2(0.12345, 0.12345); // To avoid some zeros |
| 97 | + vector2 rr = vector2(radians(rotation_range.x), radians(rotation_range.y)); |
| 98 | + vector rotations, scales; |
| 99 | + |
| 100 | + for (int i = 0; i < 3; i++) |
| 101 | + { |
| 102 | + ctr[i] = vector2(float(id_x[i]) + float(id_y[i]) * 0.5, float(id_y[i]) / 1.15470054) / sqrt3_2; |
| 103 | + rand[i] = mx_hextile_hash(vector2(float(id_x[i]), float(id_y[i])) + seed_offset); |
| 104 | + rotations[i] = mix(rr.x, rr.y, rand[i].x * rotation); |
| 105 | + scales[i] = mix(1.0, mix(scale_range.x, scale_range.y, rand[i].y), scale); |
| 106 | + offsets[i] = vector2( |
| 107 | + mix(offset_range.x, offset_range.y, rand[i].x * offset), |
| 108 | + mix(offset_range.x, offset_range.y, rand[i].y * offset)); |
| 109 | + } |
| 110 | + |
| 111 | + vector sin_r = sin(rotations); |
| 112 | + vector cos_r = cos(rotations); |
| 113 | + |
| 114 | + HextileData tile_data; |
| 115 | + tile_data.weights = vector(w1, w2, w3); |
| 116 | + tile_data.rotations = rotations; |
| 117 | + |
| 118 | + // Transform coordinates for each tile |
| 119 | + for (int i = 0; i < 3; i++) |
| 120 | + { |
| 121 | + vector2 d = coord - ctr[i]; |
| 122 | + float c = cos_r[i]; |
| 123 | + float s = sin_r[i]; |
| 124 | + float sc = scales[i]; |
| 125 | + |
| 126 | + tile_data.coords[i] = vector2( |
| 127 | + (d.x * c - d.y * s) / sc + ctr[i].x + offsets[i].x, |
| 128 | + (d.x * s + d.y * c) / sc + ctr[i].y + offsets[i].y); |
| 129 | + } |
| 130 | + |
| 131 | + return tile_data; |
| 132 | +} |
| 133 | + |
| 134 | +// Blend 3 normals by blending the gradients |
| 135 | +// Morten S. Mikkelsen, Surface Gradient-Based Bump Mapping Framework, Journal of |
| 136 | +// Computer Graphics Techniques (JCGT), vol. 9, no. 3, 60-90, 2020 |
| 137 | +// http://jcgt.org/published/0009/03/04/ |
| 138 | +vector mx_normals_to_gradient(vector N, vector Np) |
| 139 | +{ |
| 140 | + float d = dot(N, Np); |
| 141 | + vector g = (d * N - Np) / max(1e-8, abs(d)); |
| 142 | + return g; |
| 143 | +} |
| 144 | + |
| 145 | +vector mx_gradient_blend_3_normals(vector N, vector N1, float N1_weight, vector N2, float N2_weight, vector N3, float N3_weight) |
| 146 | +{ |
| 147 | + float w1 = clamp(N1_weight, 0.0, 1.0); |
| 148 | + float w2 = clamp(N2_weight, 0.0, 1.0); |
| 149 | + float w3 = clamp(N3_weight, 0.0, 1.0); |
| 150 | + |
| 151 | + vector g1 = mx_normals_to_gradient(N, N1); |
| 152 | + vector g2 = mx_normals_to_gradient(N, N2); |
| 153 | + vector g3 = mx_normals_to_gradient(N, N3); |
| 154 | + |
| 155 | + // Blend |
| 156 | + vector gg = w1 * g1 + w2 * g2 + w3 * g3; |
| 157 | + |
| 158 | + // Gradient to normal |
| 159 | + return normalize(N - gg); |
| 160 | +} |
| 161 | + |
| 162 | +// Axis-angle rotation matrix |
| 163 | +matrix mx_axis_rotation_matrix(vector axis, float angle) |
| 164 | +{ |
| 165 | + float s = sin(angle); |
| 166 | + float c = cos(angle); |
| 167 | + float omc = 1.0 - c; |
| 168 | + vector a = axis; |
| 169 | + |
| 170 | + return matrix( |
| 171 | + a[0]*a[0]*omc + c, a[0]*a[1]*omc - a[2]*s, a[0]*a[2]*omc + a[1]*s, 0.0, |
| 172 | + a[1]*a[0]*omc + a[2]*s, a[1]*a[1]*omc + c, a[1]*a[2]*omc - a[0]*s, 0.0, |
| 173 | + a[2]*a[0]*omc - a[1]*s, a[2]*a[1]*omc + a[0]*s, a[2]*a[2]*omc + c, 0.0, |
| 174 | + 0.0, 0.0, 0.0, 1.0 |
| 175 | + ); |
| 176 | +} |
0 commit comments