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
|
#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;
}
struct AperiodicTileSample {
float coverage;
float2 barycentric;
};
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 tile5_distance_from_barycentric(float2 barycentric) {
float2 f = abs(barycentric);
return 0.5 - max(f.x, f.y);
}
float aperiodic_edge_coverage(float distance_to_edge) {
float edge_width = saturate(_Aperiodic_Tiling_Edge_Thickness * 2.0) * 0.5;
float signed_distance = distance_to_edge - edge_width;
float aa_width = max(fwidth(signed_distance), 1e-4);
return smoothstep(-aa_width, aa_width, signed_distance);
}
#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 choose_aperiodic_candidate(float2 barycentric, inout float distance_to_edge,
inout float2 best_barycentric) {
float candidate_distance = tile5_distance_from_barycentric(barycentric);
if (candidate_distance > distance_to_edge) {
distance_to_edge = candidate_distance;
best_barycentric = barycentric;
}
}
AperiodicTileSample sample_aperiodic_orientation(
float4 p03, float p44, float4 a03, float a44, float4 b03, float b44) {
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 best_barycentric = tile5_barycentric(p03, p44, m, r + s.xx, a03, a44, b03, b44);
float distance_to_edge = tile5_distance_from_barycentric(best_barycentric);
choose_aperiodic_candidate(
tile5_barycentric(p03, p44, m, r + s.xy, a03, a44, b03, b44),
distance_to_edge, best_barycentric);
choose_aperiodic_candidate(
tile5_barycentric(p03, p44, m, r + s.yx, a03, a44, b03, b44),
distance_to_edge, best_barycentric);
choose_aperiodic_candidate(
tile5_barycentric(p03, p44, m, r + s.yy, a03, a44, b03, b44),
distance_to_edge, best_barycentric);
AperiodicTileSample sample;
sample.coverage = aperiodic_edge_coverage(distance_to_edge);
sample.barycentric = best_barycentric;
return sample;
}
void apply_aperiodic_orientation(float3 face_color,
float4 p03, float p44,
float4 a03, float a44, float4 b03, float b44,
float3 edge_color,
inout float3 albedo, inout float2 normal_xy_acc) {
AperiodicTileSample sample =
sample_aperiodic_orientation(p03, p44, a03, a44, b03, b44);
albedo += (face_color - edge_color) * sample.coverage;
#if defined(_APERIODIC_TILING_NORMALS)
normal_xy_acc += aperiodic_tiling_normal(sample.barycentric).xy * sample.coverage;
#endif
}
void sample_aperiodic_tiling(float2 uv, out 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 u = float2(0, 1);
float3 edge = _Aperiodic_Tiling_Edge_Color.rgb;
albedo = edge;
float2 normal_xy_accum = 0;
// 10 face orientations = C(5,2) pairs of 5D axes
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_0.rgb, p03, p44, u.yxxx, u.x, u.xyxx, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_1.rgb, p03, p44, u.yxxx, u.x, u.xxyx, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_2.rgb, p03, p44, u.yxxx, u.x, u.xxxy, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_3.rgb, p03, p44, u.yxxx, u.x, u.xxxx, u.y, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_4.rgb, p03, p44, u.xyxx, u.x, u.xxyx, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_5.rgb, p03, p44, u.xyxx, u.x, u.xxxy, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_6.rgb, p03, p44, u.xyxx, u.x, u.xxxx, u.y, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_7.rgb, p03, p44, u.xxyx, u.x, u.xxxy, u.x, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_8.rgb, p03, p44, u.xxyx, u.x, u.xxxx, u.y, edge, albedo, normal_xy_accum);
apply_aperiodic_orientation(_Aperiodic_Tiling_Color_9.rgb, p03, p44, u.xxxy, u.x, u.xxxx, u.y, edge, albedo, normal_xy_accum);
#if defined(_APERIODIC_TILING_NORMALS)
tiling_normal_tangent = normalize(float3(normal_xy_accum, 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 = blendNormalsHill12(normal_tangent, tiling_normal_tangent);
#endif
#endif // _APERIODIC_TILING
}
#endif // __APERIODIC_TILING_INC
|