yum/3ner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum/3ner

yumaperiodic: delete dead code4a3c970

master
15.2 KiB434 linesraw
1#ifndef __APERIODIC_TILING_INC
2#define __APERIODIC_TILING_INC
3
4// 5D cut-and-project for Penrose tiling.
5// References:
6//   https://www.shadertoy.com/view/XccXW8 (public domain)
7//   https://gglouser.github.io/cut-and-project-tiling/docs/intro.html#how-it-works
8//   https://www.youtube.com/watch?v=hwMAOFb6yvA
9
10#include "globals.cginc"
11#include "math.cginc"
12
13#if defined(_APERIODIC_TILING)
14
15static const float M5 = sqrt(2.0 / 5.0);
16static const float APERIODIC_FILTER_THRESHOLD = 0.5;
17static const float APERIODIC_FAR_THRESHOLD = 1.0;
18static const float APERIODIC_FILTER_BLEND_WIDTH = 0.5;
19static const float APERIODIC_SMOOTHSTEP_SQ_MEAN = 13.0 / 35.0;
20
21static const float4 basis_u5_03 = M5 * float4(
22  cos(0 * TAU / 10),
23  cos(1 * TAU / 10),
24  cos(2 * TAU / 10),
25  cos(3 * TAU / 10));
26static const float basis_u5_44 = M5 * cos(4 * TAU / 10);
27
28static const float4 basis_v5_03 = M5 * float4(
29  sin(0 * TAU / 10),
30  sin(1 * TAU / 10),
31  sin(2 * TAU / 10),
32  sin(3 * TAU / 10));
33static const float basis_v5_44 = M5 * sin(4 * TAU / 10);
34
35static const float2 aperiodic_tile_offsets[4] = {
36  float2(0.5, 0.5),
37  float2(0.5, -0.5),
38  float2(-0.5, 0.5),
39  float2(-0.5, -0.5)
40};
41
42static const float4 aperiodic_face_a03[10] = {
43  float4(1, 0, 0, 0),
44  float4(1, 0, 0, 0),
45  float4(1, 0, 0, 0),
46  float4(1, 0, 0, 0),
47  float4(0, 1, 0, 0),
48  float4(0, 1, 0, 0),
49  float4(0, 1, 0, 0),
50  float4(0, 0, 1, 0),
51  float4(0, 0, 1, 0),
52  float4(0, 0, 0, 1)
53};
54
55static const float aperiodic_face_a44[10] = {
56  0, 0, 0, 0, 0, 0, 0, 0, 0, 0
57};
58
59static const float4 aperiodic_face_b03[10] = {
60  float4(0, 1, 0, 0),
61  float4(0, 0, 1, 0),
62  float4(0, 0, 0, 1),
63  float4(0, 0, 0, 0),
64  float4(0, 0, 1, 0),
65  float4(0, 0, 0, 1),
66  float4(0, 0, 0, 0),
67  float4(0, 0, 0, 1),
68  float4(0, 0, 0, 0),
69  float4(0, 0, 0, 0)
70};
71
72static const float aperiodic_face_b44[10] = {
73  0, 0, 0, 1, 0, 0, 1, 0, 1, 1
74};
75
76static const float4 aperiodic_face_c03[10] = {
77  float4(0, 0, 1, 1),
78  float4(0, 1, 0, 1),
79  float4(0, 1, 1, 0),
80  float4(0, 1, 1, 1),
81  float4(1, 0, 0, 1),
82  float4(1, 0, 1, 0),
83  float4(1, 0, 1, 1),
84  float4(1, 1, 0, 0),
85  float4(1, 1, 0, 1),
86  float4(1, 1, 1, 0)
87};
88
89static const float aperiodic_face_c44[10] = {
90  1, 1, 1, 0, 1, 1, 0, 1, 0, 0
91};
92
93// Area weights for the 10 face orientations. Adjacent axis pairs are the thin
94// rhombs, separated pairs are the thick rhombs. These weights sum to 1.
95static const float aperiodic_face_weights[10] = {
96  0.0552786404500042,
97  0.1447213595499958,
98  0.1447213595499958,
99  0.0552786404500042,
100  0.0552786404500042,
101  0.1447213595499958,
102  0.1447213595499958,
103  0.0552786404500042,
104  0.1447213595499958,
105  0.0552786404500042
106};
107
108// Precomputed per-face barycentric transforms. For each fixed face
109// orientation, this is inverse(float2x2(proj5(a), proj5(b))).
110static const float2x2 aperiodic_face_matrices[10] = {
111  float2x2(1.5811388300841898, -2.1762508994828216, -0.0, 2.6899940478558295),
112  float2x2(1.5811388300841895, -0.5137431483730078, -0.0, 1.6625077511098139),
113  float2x2(1.5811388300841895, 0.5137431483730076, -0.0, 1.6625077511098136),
114  float2x2(1.5811388300841895, 2.176250899482821, -0.0, 2.6899940478558286),
115  float2x2(2.558336368008464, -0.8312538755549072, -1.58113883008419, 2.176250899482822),
116  float2x2(1.5811388300841895, 0.5137431483730076, -0.9771975379242739, 1.3449970239279145),
117  float2x2(0.977197537924274, 1.3449970239279145, -0.9771975379242739, 1.3449970239279145),
118  float2x2(2.558336368008464, 0.8312538755549067, -2.5583363680084634, 0.8312538755549069),
119  float2x2(0.9771975379242742, 1.3449970239279148, -1.5811388300841895, 0.5137431483730078),
120  float2x2(1.5811388300841898, 2.176250899482821, -2.5583363680084634, -0.8312538755549066)
121};
122
123float dot5(float4 a03, float a44, float4 b03, float b44) {
124  return dot(a03, b03) + a44 * b44;
125}
126
127float2 proj5(float4 p03, float p44) {
128  return float2(
129    dot5(p03, p44, basis_u5_03, basis_u5_44),
130    dot5(p03, p44, basis_v5_03, basis_v5_44));
131}
132
133float3 aperiodic_face_color(int face_id) {
134  if (face_id == 0) return _Aperiodic_Tiling_Color_0.rgb;
135  if (face_id == 1) return _Aperiodic_Tiling_Color_1.rgb;
136  if (face_id == 2) return _Aperiodic_Tiling_Color_2.rgb;
137  if (face_id == 3) return _Aperiodic_Tiling_Color_3.rgb;
138  if (face_id == 4) return _Aperiodic_Tiling_Color_4.rgb;
139  if (face_id == 5) return _Aperiodic_Tiling_Color_5.rgb;
140  if (face_id == 6) return _Aperiodic_Tiling_Color_6.rgb;
141  if (face_id == 7) return _Aperiodic_Tiling_Color_7.rgb;
142  if (face_id == 8) return _Aperiodic_Tiling_Color_8.rgb;
143  return _Aperiodic_Tiling_Color_9.rgb;
144}
145
146float2 aperiodic_tile_barycentric(float2 uv_m, float2x2 m, float2 s, float2 q,
147                                  float4 c03, float c44) {
148  float4 shift03 = c03 * round(q.x * basis_u5_03 + q.y * basis_v5_03);
149  float shift44 = c44 * round(q.x * basis_u5_44 + q.y * basis_v5_44);
150  return uv_m - mul(m, proj5(shift03, shift44)) - s;
151}
152
153float interval_box_coverage(float center, float half_extent, float radius) {
154  if (half_extent <= 1e-5) {
155    return abs(center) <= radius ? 1.0 : 0.0;
156  }
157
158  float lo = center - half_extent;
159  float hi = center + half_extent;
160  float overlap = max(min(hi, radius) - max(lo, -radius), 0.0);
161  return overlap / (2.0 * half_extent);
162}
163
164float square_box_coverage(float2 barycentric, float2 half_extents, float radius) {
165  return interval_box_coverage(barycentric.x, half_extents.x, radius) *
166         interval_box_coverage(barycentric.y, half_extents.y, radius);
167}
168
169#if defined(_APERIODIC_TILING_NORMALS)
170float3 aperiodic_tiling_normal(float2 barycentric) {
171  float bevel_width = min(_Aperiodic_Tiling_Normal_Thickness, 0.5);
172  if (bevel_width <= 1e-5) {
173    return float3(0.0, 0.0, 1.0);
174  }
175
176  float flat_limit = 0.5 - bevel_width;
177  float2 edge_factor = smoothstep(flat_limit, 0.5, abs(barycentric));
178  float2 xy = sign(barycentric) * edge_factor * _Aperiodic_Tiling_Normal_Strength;
179  return normalize(float3(xy, 1.0));
180}
181#endif  // _APERIODIC_TILING_NORMALS
182
183struct AperiodicPointSample {
184  float distance_to_edge;
185  float2 barycentric;
186  int face_id;
187};
188
189struct AperiodicFootprintRange {
190  float min_extent;
191  float max_extent;
192};
193
194void aperiodic_accumulate_point_orientation(float2 uv, float4 p03, float p44, int face_id,
195                                            inout AperiodicPointSample best) {
196  float2x2 m = aperiodic_face_matrices[face_id];
197  float4 a03 = aperiodic_face_a03[face_id];
198  float a44 = aperiodic_face_a44[face_id];
199  float4 b03 = aperiodic_face_b03[face_id];
200  float b44 = aperiodic_face_b44[face_id];
201  float4 c03 = aperiodic_face_c03[face_id];
202  float c44 = aperiodic_face_c44[face_id];
203
204  float2 r = round(float2(dot5(p03, p44, a03, a44), dot5(p03, p44, b03, b44)));
205  float2 uv_m = mul(m, uv);
206
207  [unroll]
208  for (int candidate_id = 0; candidate_id < 4; ++candidate_id) {
209    float2 s = r + aperiodic_tile_offsets[candidate_id];
210    float2 q = mul(s, m);
211    float2 barycentric = aperiodic_tile_barycentric(uv_m, m, s, q, c03, c44);
212    float distance_to_edge = 0.5 - max(abs(barycentric.x), abs(barycentric.y));
213    bool better = distance_to_edge > best.distance_to_edge;
214    best.distance_to_edge = better ? distance_to_edge : best.distance_to_edge;
215    best.barycentric = better ? barycentric : best.barycentric;
216    best.face_id = better ? face_id : best.face_id;
217  }
218}
219
220void sample_aperiodic_tiling_point(float2 uv, float4 p03, float p44, out float3 albedo,
221                                   out float3 tiling_normal_tangent) {
222  AperiodicPointSample best;
223  best.distance_to_edge = -1e10;
224  best.barycentric = 0.0;
225  best.face_id = 0;
226
227  [unroll]
228  for (int face_id = 0; face_id < 10; ++face_id) {
229    aperiodic_accumulate_point_orientation(uv, p03, p44, face_id, best);
230  }
231
232  float edge_width = min(_Aperiodic_Tiling_Edge_Thickness, 0.5);
233  float edge_sd = best.distance_to_edge - edge_width;
234  float edge_sd_aa = max(abs(fwidth(edge_sd)), 1e-4);
235  float edge_mask = smoothstep(-edge_sd_aa * 0.5, edge_sd_aa * 0.5, edge_sd);
236  albedo = lerp(_Aperiodic_Tiling_Edge_Color.rgb, aperiodic_face_color(best.face_id), edge_mask);
237
238#if defined(_APERIODIC_TILING_NORMALS)
239  float3 tile_normal = aperiodic_tiling_normal(best.barycentric);
240  tiling_normal_tangent = normalize(float3(tile_normal.xy * edge_mask, 1.0));
241#else
242  tiling_normal_tangent = 0.0;
243#endif
244}
245
246AperiodicFootprintRange aperiodic_footprint_range(float2 uv_ddx, float2 uv_ddy) {
247  AperiodicFootprintRange range;
248  range.min_extent = 1e10;
249  range.max_extent = 0.0;
250
251  [unroll]
252  for (int face_id = 0; face_id < 10; ++face_id) {
253    float2x2 m = aperiodic_face_matrices[face_id];
254    float2 dbdx = mul(m, uv_ddx);
255    float2 dbdy = mul(m, uv_ddy);
256    float face_extent = max(abs(dbdx.x) + abs(dbdy.x), abs(dbdx.y) + abs(dbdy.y)) * 0.5;
257    range.min_extent = min(range.min_extent, face_extent);
258    range.max_extent = max(range.max_extent, face_extent);
259  }
260
261  return range;
262}
263
264void aperiodic_accumulate_filtered_orientation(float2 uv, float4 p03, float p44,
265                                               float2 uv_ddx, float2 uv_ddy, int face_id,
266                                               float inner_radius, inout float3 face_color_sum,
267                                               inout float face_sum) {
268  float2x2 m = aperiodic_face_matrices[face_id];
269  float4 a03 = aperiodic_face_a03[face_id];
270  float a44 = aperiodic_face_a44[face_id];
271  float4 b03 = aperiodic_face_b03[face_id];
272  float b44 = aperiodic_face_b44[face_id];
273  float4 c03 = aperiodic_face_c03[face_id];
274  float c44 = aperiodic_face_c44[face_id];
275
276  float2 r = round(float2(dot5(p03, p44, a03, a44), dot5(p03, p44, b03, b44)));
277  float2 uv_m = mul(m, uv);
278  float2 dbdx = mul(m, uv_ddx);
279  float2 dbdy = mul(m, uv_ddy);
280  float2 half_extents = 0.5 * (abs(dbdx) + abs(dbdy));
281  float2 inner_limit = half_extents + inner_radius;
282  float3 face_color = aperiodic_face_color(face_id);
283
284  [unroll]
285  for (int candidate_id = 0; candidate_id < 4; ++candidate_id) {
286    float2 s = r + aperiodic_tile_offsets[candidate_id];
287    float2 q = mul(s, m);
288    float2 barycentric = aperiodic_tile_barycentric(uv_m, m, s, q, c03, c44);
289
290    if (abs(barycentric.x) >= inner_limit.x || abs(barycentric.y) >= inner_limit.y) {
291      continue;
292    }
293
294    float inner = square_box_coverage(barycentric, half_extents, inner_radius);
295    face_color_sum += face_color * inner;
296    face_sum += inner;
297  }
298}
299
300void sample_aperiodic_tiling_filtered(float2 uv, float4 p03, float p44, float2 uv_ddx,
301                                      float2 uv_ddy, out float3 albedo,
302                                      out float3 tiling_normal_tangent) {
303  float inner_radius = max(0.5 - min(_Aperiodic_Tiling_Edge_Thickness, 0.5), 0.0);
304  float3 face_color_sum = 0.0;
305  float face_sum = 0.0;
306
307  [unroll]
308  for (int face_id = 0; face_id < 10; ++face_id) {
309    aperiodic_accumulate_filtered_orientation(
310        uv, p03, p44, uv_ddx, uv_ddy, face_id, inner_radius, face_color_sum, face_sum);
311  }
312
313  float edge_weight = saturate(1.0 - face_sum);
314  albedo = face_color_sum + _Aperiodic_Tiling_Edge_Color.rgb * edge_weight;
315
316#if defined(_APERIODIC_TILING_NORMALS)
317  // In the filtered regime the bevel is subpixel detail, so keep the normal flat.
318  tiling_normal_tangent = float3(0.0, 0.0, 1.0);
319#else
320  tiling_normal_tangent = 0.0;
321#endif
322}
323
324float3 sample_aperiodic_tiling_far_albedo() {
325  float3 face_mean = 0.0;
326
327  [unroll]
328  for (int face_id = 0; face_id < 10; ++face_id) {
329    face_mean += aperiodic_face_color(face_id) * aperiodic_face_weights[face_id];
330  }
331
332  float inner_width = saturate(1.0 - 2.0 * min(_Aperiodic_Tiling_Edge_Thickness, 0.5));
333  float inner_fraction = inner_width * inner_width;
334  return lerp(_Aperiodic_Tiling_Edge_Color.rgb, face_mean, inner_fraction);
335}
336
337float aperiodic_minified_normal_kernel_roughness(float minified_weight) {
338#if defined(_APERIODIC_TILING_NORMALS)
339  float bevel_width = min(_Aperiodic_Tiling_Normal_Thickness, 0.5);
340  if (bevel_width <= 1e-5 || minified_weight <= 1e-5) {
341    return 0.0;
342  }
343
344  float flat_width = saturate(1.0 - 2.0 * bevel_width);
345  float bevel_fraction = 1.0 - flat_width * flat_width;
346  float slope_variance =
347      _Aperiodic_Tiling_Normal_Strength * _Aperiodic_Tiling_Normal_Strength *
348      bevel_fraction * APERIODIC_SMOOTHSTEP_SQ_MEAN;
349  return minified_weight * min(2.0 * slope_variance, _Specular_AA_Threshold);
350#else
351  return 0.0;
352#endif
353}
354
355void apply_aperiodic_smoothness(inout float smoothness, float minified_weight) {
356  float kernel_roughness = aperiodic_minified_normal_kernel_roughness(minified_weight);
357  if (kernel_roughness <= 1e-5) {
358    return;
359  }
360
361  float perceptual_roughness = 1.0 - smoothness;
362  float roughness = perceptual_roughness * perceptual_roughness;
363  float square_roughness = saturate(roughness * roughness + kernel_roughness);
364  smoothness = 1.0 - saturate(sqrt(sqrt(square_roughness)));
365}
366
367void sample_aperiodic_tiling_minified(float2 uv, float4 p03, float p44, float2 uv_ddx,
368                                      float2 uv_ddy, float far_weight, out float3 albedo,
369                                      out float3 tiling_normal_tangent) {
370  float3 filtered_albedo;
371  float3 filtered_normal_tangent;
372  float3 far_albedo = sample_aperiodic_tiling_far_albedo();
373
374  sample_aperiodic_tiling_filtered(
375      uv, p03, p44, uv_ddx, uv_ddy, filtered_albedo, filtered_normal_tangent);
376  albedo = lerp(filtered_albedo, far_albedo, far_weight);
377
378#if defined(_APERIODIC_TILING_NORMALS)
379  tiling_normal_tangent = normalize(lerp(
380      filtered_normal_tangent, float3(0.0, 0.0, 1.0), far_weight));
381#else
382  tiling_normal_tangent = 0.0;
383#endif
384}
385
386void sample_aperiodic_tiling(float2 uv, out float3 albedo, out float3 tiling_normal_tangent,
387                             out float minified_weight) {
388  float4 p03 = uv.x * basis_u5_03 + uv.y * basis_v5_03;
389  float p44 = uv.x * basis_u5_44 + uv.y * basis_v5_44;
390  float2 uv_ddx = ddx(uv);
391  float2 uv_ddy = ddy(uv);
392  AperiodicFootprintRange footprint = aperiodic_footprint_range(uv_ddx, uv_ddy);
393  float filter_end = APERIODIC_FILTER_THRESHOLD + APERIODIC_FILTER_BLEND_WIDTH;
394  float far_weight = smoothstep(
395      APERIODIC_FILTER_THRESHOLD, APERIODIC_FAR_THRESHOLD, footprint.min_extent);
396
397  [branch]
398  if (footprint.max_extent >= filter_end) {
399    sample_aperiodic_tiling_minified(
400        uv, p03, p44, uv_ddx, uv_ddy, far_weight, albedo, tiling_normal_tangent);
401    minified_weight = 1.0;
402  } else {
403    float3 point_albedo;
404    float3 point_normal_tangent;
405    float3 minified_albedo;
406    float3 minified_normal_tangent;
407    float filter_weight = smoothstep(0.0, filter_end, footprint.max_extent);
408
409    sample_aperiodic_tiling_point(uv, p03, p44, point_albedo, point_normal_tangent);
410    sample_aperiodic_tiling_minified(
411        uv, p03, p44, uv_ddx, uv_ddy, far_weight, minified_albedo, minified_normal_tangent);
412    albedo = lerp(point_albedo, minified_albedo, filter_weight);
413    tiling_normal_tangent = normalize(lerp(
414        point_normal_tangent, minified_normal_tangent, filter_weight));
415    minified_weight = filter_weight;
416  }
417}
418#endif  // defined(_APERIODIC_TILING)
419
420void apply_aperiodic_tiling(float2 uv, inout float3 albedo, inout float smoothness,
421                            inout float3 normal_tangent) {
422#if defined(_APERIODIC_TILING)
423  uv *= _Aperiodic_Tiling_Scale;
424  float3 tiling_normal_tangent;
425  float minified_weight;
426  sample_aperiodic_tiling(uv, albedo, tiling_normal_tangent, minified_weight);
427#if defined(_APERIODIC_TILING_NORMALS)
428  normal_tangent = tiling_normal_tangent;
429  apply_aperiodic_smoothness(smoothness, minified_weight);
430#endif
431#endif  // _APERIODIC_TILING
432}
433
434#endif  // __APERIODIC_TILING_INC