fs_hextile.sc 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. $input v_position, v_texcoord0
  2. /*
  3. * Copyright 2022 Preetish Kakkar. All rights reserved.
  4. * License: https://github.com/bkaradzic/bgfx/blob/master/LICENSE
  5. */
  6. /*
  7. Most of the code is inspired/ported from https://github.com/mmikk/hextile-demo/blob/main/hextile-demo/shader_lighting.hlsl
  8. The basic idea behind the algorithm is to use tiling & blending schema but instead of regular linear blending, the algorithm uses blending operator that prevents visual artifacts caused by linear blending
  9. We partition the uv-space on a triangle grid and compute the local triangle and the barycentric coordinates inside the triangle. We use a hash function to associate a random offset with each vertex of the triangle
  10. grid and use this random offset to fetch the example texture.
  11. Finally, we blend the result using the barycentric coordinates as blending weights.
  12. */
  13. #include "../common/common.sh"
  14. #define M_PI 3.1415926535897932384626433832795
  15. SAMPLER2D(s_trx_d, 0);
  16. uniform vec4 u_params;
  17. #ifndef fmod
  18. #define fmod(x, y) (x - y * trunc(x / y))
  19. #endif
  20. #define moduleOper(a, b) a - (float(b) * floor(a/float(b)))
  21. #define u_showWeights u_params.x
  22. #define u_tileRate u_params.y
  23. #define u_tileRotStrength u_params.z
  24. #define u_useRegularTiling u_params.w
  25. vec3 Gain3(vec3 x, float r)
  26. {
  27. // increase contrast when r>0.5 and
  28. // reduce contrast if less
  29. float k = log(1.0 - r) / log(0.5);
  30. vec3 s = 2.0 * step(0.5, x);
  31. vec3 m = 2.0 * (1.0 - s);
  32. vec3 res = 0.5 * s + 0.25 * m * pow(max(vec3_splat(0.0), s + x * m), vec3_splat(k));
  33. return res.xyz / (res.x + res.y + res.z);
  34. }
  35. mat2 LoadRot2x2(vec2 idx, float rotStrength)
  36. {
  37. float angle = abs(idx.x * idx.y) + abs(idx.x + idx.y) + M_PI;
  38. // remap to +/-pi
  39. //angle = fmod(angle, 2.0*M_PI);
  40. if (angle < 0.0) angle += 2.0 * M_PI;
  41. if (angle > M_PI) angle -= 2.0 * M_PI;
  42. angle *= rotStrength;
  43. float cs = cos(angle);
  44. float si = sin(angle);
  45. return mat2(cs, -si, si, cs);
  46. }
  47. vec2 MakeCenST(vec2 Vertex)
  48. {
  49. mat2 invSkewMat = mat2(1.0, 0.5, 0.0, 1.0 / 1.15470054);
  50. return mul(invSkewMat, Vertex) / (2.0 * sqrt(3.0));
  51. }
  52. vec3 ProduceHexWeights(vec3 W, vec2 vertex1, vec2 vertex2, vec2 vertex3)
  53. {
  54. vec3 res = vec3_splat(0.0);
  55. float v1 = moduleOper(((vertex1.x - vertex1.y)), 3.0);
  56. if (v1 < 0.0) v1 += 3.0;
  57. float vh = v1 < 2.0 ? (v1 + 1.0) : 0.0;
  58. float vl = v1 > 0.0 ? (v1 - 1.0) : 2.0;
  59. float v2 = vertex1.x < vertex3.x ? vl : vh;
  60. float v3 = vertex1.x < vertex3.x ? vh : vl;
  61. res.x = v3 == 0.0 ? W.z : (v2 == 0.0 ? W.y : W.x);
  62. res.y = v3 == 1.0 ? W.z : (v2 == 1.0 ? W.y : W.x);
  63. res.z = v3 == 2.0 ? W.z : (v2 == 2.0 ? W.y : W.x);
  64. return res;
  65. }
  66. vec2 hash(vec2 p)
  67. {
  68. vec2 r = mul(mat2(127.1, 311.7, 269.5, 183.3), p);
  69. return fract(sin(r) * 43758.5453);
  70. }
  71. // Given a point in UV, compute local triangle barycentric coordinates and vertex IDs
  72. void TriangleGrid(out float w1, out float w2, out float w3,
  73. out vec2 vertex1, out vec2 vertex2, out vec2 vertex3,
  74. vec2 uv)
  75. {
  76. // Scaling of the input
  77. uv *= 2.0 * sqrt(3.0); // controls the size of the input with respect to the size of the tiles.
  78. // Skew input space into simplex triangle grid
  79. const mat2 gridToSkewedGrid =
  80. mat2(1.0, -0.57735027, 0.0, 1.15470054);
  81. vec2 skewedCoord = mul(gridToSkewedGrid, uv);
  82. vec2 baseId = floor(skewedCoord);
  83. vec3 temp = vec3(fract(skewedCoord), 0.0);
  84. temp.z = 1.0 - temp.x - temp.y;
  85. float s = step(0.0, -temp.z);
  86. float s2 = 2.0 * s - 1.0;
  87. w1 = -temp.z * s2;
  88. w2 = s - temp.y * s2;
  89. w3 = s - temp.x * s2;
  90. vertex1 = baseId + vec2(s, s);
  91. vertex2 = baseId + vec2(s, 1.0 - s);
  92. vertex3 = baseId + vec2(1.0 - s, s);
  93. }
  94. void hex2colTex(out vec4 color, out vec3 weights, vec2 uv,
  95. float rotStrength, float r)
  96. {
  97. // compute uv derivatives
  98. vec2 dSTdx = dFdx(uv), dSTdy = dFdy(uv);
  99. // Get triangle info
  100. float w1, w2, w3;
  101. vec2 vertex1, vertex2, vertex3;
  102. TriangleGrid(w1, w2, w3, vertex1, vertex2, vertex3, uv);
  103. mat2 rot1 = LoadRot2x2(vertex1, rotStrength);
  104. mat2 rot2 = LoadRot2x2(vertex2, rotStrength);
  105. mat2 rot3 = LoadRot2x2(vertex3, rotStrength);
  106. vec2 cen1 = MakeCenST(vertex1);
  107. vec2 cen2 = MakeCenST(vertex2);
  108. vec2 cen3 = MakeCenST(vertex3);
  109. // assign random offset to each triangle vertex
  110. // this is used later to fetch from texture
  111. vec2 uv1 = mul(uv - cen1, rot1) + cen1 + hash(vertex1);
  112. vec2 uv2 = mul(uv - cen2, rot2) + cen2 + hash(vertex2);
  113. vec2 uv3 = mul(uv - cen3, rot3) + cen3 + hash(vertex3);
  114. // Fetch input
  115. // We could simply use texture2D function, however, the screen space derivatives could be broken
  116. // since we are using random offsets, we use texture2DGrad to make sure that we pass correct derivatives explicitly.
  117. vec4 c1 = texture2DGrad(s_trx_d, uv1,
  118. mul(dSTdx, rot1), mul(dSTdy, rot1));
  119. vec4 c2 = texture2DGrad(s_trx_d, uv2,
  120. mul(dSTdx, rot2), mul(dSTdy, rot2));
  121. vec4 c3 = texture2DGrad(s_trx_d, uv3,
  122. mul(dSTdx, rot3), mul(dSTdy, rot3));
  123. // use luminance as weight
  124. vec3 Lw = vec3(0.299, 0.587, 0.114);
  125. vec3 Dw = vec3(dot(c1.xyz, Lw), dot(c2.xyz, Lw), dot(c3.xyz, Lw));
  126. Dw = mix(vec3_splat(1.0), Dw, 0.6); // 0.6 is fall off constant
  127. vec3 W = Dw * pow(vec3(w1, w2, w3), vec3_splat(7.0)); // 7 is g_exp
  128. W /= (W.x + W.y + W.z);
  129. if (r != 0.5) W = Gain3(W, r);
  130. // blend weights with color linearly
  131. // histogram preserving blending will be better but requires precompution step to create histogram texture
  132. color = W.x * c1 + W.y * c2 + W.z * c3;
  133. weights = ProduceHexWeights(W.xyz, vertex1, vertex2, vertex3);
  134. }
  135. float GetTileRate()
  136. {
  137. return 0.05 * u_tileRate;
  138. }
  139. void FetchColorAndWeight(out vec3 color, out vec3 weights, vec2 uv)
  140. {
  141. vec4 col4;
  142. hex2colTex(col4, weights, uv, u_tileRotStrength, 0.7);
  143. color = col4.xyz;
  144. }
  145. void main()
  146. {
  147. // actual world space position
  148. vec3 surfPosInWorld = v_position.xyz;
  149. vec3 sp = GetTileRate() * surfPosInWorld;
  150. vec2 uv0 = vec2(sp.x, sp.z);
  151. if(u_useRegularTiling > 0.0)
  152. {
  153. gl_FragColor = vec4(texture2D(s_trx_d, uv0.xy));
  154. }
  155. else
  156. {
  157. vec3 color, weights;
  158. FetchColorAndWeight(color, weights, uv0);
  159. if (u_showWeights > 0.0)
  160. {
  161. gl_FragColor = vec4(weights, 1.0);
  162. }
  163. else
  164. {
  165. gl_FragColor = vec4(color, 1.0);
  166. }
  167. }
  168. }