summaryrefslogtreecommitdiffstats
path: root/aperiodic_tiling.cginc
blob: 1907c69468a0ef25ede5abb77038f080e841c6f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
#ifndef __APERIODIC_TILING_INC
#define __APERIODIC_TILING_INC

// 5D cut-and-project for Penrose tiling.
// References:
//   https://www.shadertoy.com/view/XccXW8 (public domain)
//   https://gglouser.github.io/cut-and-project-tiling/docs/intro.html#how-it-works
//   https://www.youtube.com/watch?v=hwMAOFb6yvA

#include "globals.cginc"
#include "math.cginc"

#if defined(_APERIODIC_TILING)

static const float M5 = sqrt(2.0 / 5.0);

static const float4 basis_u5_03 = M5 * float4(
  cos(0 * TAU / 10),
  cos(1 * TAU / 10),
  cos(2 * TAU / 10),
  cos(3 * TAU / 10));
static const float basis_u5_44 = M5 * cos(4 * TAU / 10);

static const float4 basis_v5_03 = M5 * float4(
  sin(0 * TAU / 10),
  sin(1 * TAU / 10),
  sin(2 * TAU / 10),
  sin(3 * TAU / 10));
static const float basis_v5_44 = M5 * sin(4 * TAU / 10);

float dot5(float4 a03, float a44, float4 b03, float b44) {
  return dot(a03, b03) + a44 * b44;
}

float2 proj5(float4 p03, float p44) {
  return float2(
    dot5(p03, p44, basis_u5_03, basis_u5_44),
    dot5(p03, p44, basis_v5_03, basis_v5_44));
}

float2x2 inv2x2(float2x2 m) {
  float d = m[0][0] * m[1][1] - m[0][1] * m[1][0];
  return float2x2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / d;
}

float2 tile5_barycentric(float4 p03, float p44, float2x2 m, float2 s,
                         float4 a03, float a44, float4 b03, float b44) {
  float2 q = mul(s, m);
  p03 -= (1.0 - a03 - b03) * round(q.x * basis_u5_03 + q.y * basis_v5_03);
  p44 -= (1.0 - a44 - b44) * round(q.x * basis_u5_44 + q.y * basis_v5_44);
  return mul(m, proj5(p03, p44)) - s;
}

float interval_box_coverage(float center, float half_extent, float radius) {
  if (half_extent <= 1e-5) {
    return abs(center) <= radius ? 1.0 : 0.0;
  }

  float lo = center - half_extent;
  float hi = center + half_extent;
  float overlap = max(min(hi, radius) - max(lo, -radius), 0.0);
  return overlap / (2.0 * half_extent);
}

float square_box_coverage(float2 barycentric, float2 half_extents, float radius) {
  return interval_box_coverage(barycentric.x, half_extents.x, radius) *
         interval_box_coverage(barycentric.y, half_extents.y, radius);
}

#if defined(_APERIODIC_TILING_NORMALS)
float3 aperiodic_tiling_normal(float2 barycentric) {
  float bevel_width = min(_Aperiodic_Tiling_Normal_Thickness, 0.5);
  if (bevel_width <= 1e-5) {
    return float3(0.0, 0.0, 1.0);
  }

  float flat_limit = 0.5 - bevel_width;
  float2 edge_factor = smoothstep(flat_limit, 0.5, abs(barycentric));
  float2 xy = sign(barycentric) * edge_factor * _Aperiodic_Tiling_Normal_Strength;
  return normalize(float3(xy, 1.0));
}
#endif  // _APERIODIC_TILING_NORMALS

void accumulate_filtered_candidate(
    float2 barycentric, float2 footprint_half_extents, float edge_width, float3 face_color,
    inout float3 color_sum, inout float outer_sum, inout float2 normal_xy_sum) {
  float outer = square_box_coverage(barycentric, footprint_half_extents, 0.5);
  float inner_radius = max(0.5 - edge_width, 0.0);
  float inner = square_box_coverage(barycentric, footprint_half_extents, inner_radius);
  float edge = outer - inner;

  color_sum += face_color * inner + _Aperiodic_Tiling_Edge_Color.rgb * edge;
  outer_sum += outer;

#if defined(_APERIODIC_TILING_NORMALS)
  float3 tile_normal = aperiodic_tiling_normal(barycentric);
  normal_xy_sum += tile_normal.xy * outer;
#endif
}

void accumulate_filtered_lattice(
    float4 p03, float p44, float2 uv_ddx, float2 uv_ddy,
    float4 a03, float a44, float4 b03, float b44, float edge_width, float3 face_color,
    inout float3 color_sum, inout float outer_sum, inout float2 normal_xy_sum) {
  float2 pa = proj5(a03, a44);
  float2 pb = proj5(b03, b44);
  float2x2 m = inv2x2(float2x2(pa.x, pb.x, pa.y, pb.y));
  float2 r = round(float2(dot5(p03, p44, a03, a44), dot5(p03, p44, b03, b44)));
  float2 s = float2(0.5, -0.5);

  float2 dbdx = mul(m, uv_ddx);
  float2 dbdy = mul(m, uv_ddy);
  float2 footprint_half_extents = 0.5 * (abs(dbdx) + abs(dbdy));

  accumulate_filtered_candidate(
      tile5_barycentric(p03, p44, m, r + s.xx, a03, a44, b03, b44),
      footprint_half_extents, edge_width, face_color, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_candidate(
      tile5_barycentric(p03, p44, m, r + s.xy, a03, a44, b03, b44),
      footprint_half_extents, edge_width, face_color, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_candidate(
      tile5_barycentric(p03, p44, m, r + s.yx, a03, a44, b03, b44),
      footprint_half_extents, edge_width, face_color, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_candidate(
      tile5_barycentric(p03, p44, m, r + s.yy, a03, a44, b03, b44),
      footprint_half_extents, edge_width, face_color, color_sum, outer_sum, normal_xy_sum);
}

void sample_aperiodic_tiling(float2 uv, inout float3 albedo, out float3 tiling_normal_tangent) {
  float4 p03 = uv.x * basis_u5_03 + uv.y * basis_v5_03;
  float p44 = uv.x * basis_u5_44 + uv.y * basis_v5_44;
  float2 uv_ddx = ddx(uv);
  float2 uv_ddy = ddy(uv);

  float2 u = float2(0, 1);

  // Analytically filter the existing local tile set in each orientation's
  // barycentric space using a separable box footprint.
  float edge_width = min(_Aperiodic_Tiling_Edge_Thickness, 0.5);
  float3 color_sum = 0.0;
  float outer_sum = 0.0;
  float2 normal_xy_sum = 0.0;

  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.yxxx, u.x, u.xyxx, u.x, edge_width,
      _Aperiodic_Tiling_Color_0.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.yxxx, u.x, u.xxyx, u.x, edge_width,
      _Aperiodic_Tiling_Color_1.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.yxxx, u.x, u.xxxy, u.x, edge_width,
      _Aperiodic_Tiling_Color_2.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.yxxx, u.x, u.xxxx, u.y, edge_width,
      _Aperiodic_Tiling_Color_3.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xyxx, u.x, u.xxyx, u.x, edge_width,
      _Aperiodic_Tiling_Color_4.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xyxx, u.x, u.xxxy, u.x, edge_width,
      _Aperiodic_Tiling_Color_5.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xyxx, u.x, u.xxxx, u.y, edge_width,
      _Aperiodic_Tiling_Color_6.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xxyx, u.x, u.xxxy, u.x, edge_width,
      _Aperiodic_Tiling_Color_7.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xxyx, u.x, u.xxxx, u.y, edge_width,
      _Aperiodic_Tiling_Color_8.rgb, color_sum, outer_sum, normal_xy_sum);
  accumulate_filtered_lattice(
      p03, p44, uv_ddx, uv_ddy, u.xxxy, u.x, u.xxxx, u.y, edge_width,
      _Aperiodic_Tiling_Color_9.rgb, color_sum, outer_sum, normal_xy_sum);

  float normalization = max(outer_sum, 1e-4);
  albedo *= color_sum / normalization;

#if defined(_APERIODIC_TILING_NORMALS)
  tiling_normal_tangent = normalize(float3(normal_xy_sum / normalization, 1.0));
#else
  tiling_normal_tangent = 0;
#endif
}
#endif  // defined(_APERIODIC_TILING)

void apply_aperiodic_tiling(float2 uv, inout float3 albedo, inout float3 normal_tangent) {
#if defined(_APERIODIC_TILING)
  uv *= _Aperiodic_Tiling_Scale;
  float3 tiling_normal_tangent;
  sample_aperiodic_tiling(uv, albedo, tiling_normal_tangent);
#if defined(_APERIODIC_TILING_NORMALS)
  normal_tangent = tiling_normal_tangent;
#endif
#endif  // _APERIODIC_TILING
}

#endif  // __APERIODIC_TILING_INC