yum/3ner

A toon shader for Unity's BIRP.

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

yumrandom touchups idk i just got hereecd59e8

master
9.8 KiB343 linesraw
1#ifndef __MATH_INC
2#define __MATH_INC
3
4#include "hashwithoutsine.cginc"
5
6#define PI              3.14159265358979323846264f
7#define TAU             (2.0f * PI)
8#define HALF_PI         (PI * 0.5f)
9#define RCP_PI          (1.0f / PI)
10#define RCP_TAU         (1.0f / TAU)
11#define PHI             1.618033989f
12#define RCP_PHI         0.618033989f
13#define SQRT_2          1.414213562f
14#define SQRT_2_RCP      0.707106781f
15#define RCP_SQRT_2      0.707106781f
16#define RCP_SQRT_3      0.577350269f
17#define TWO_OVER_THREE  0.6666666666666666f
18#define TWO_OVER_SQRT_3 1.15470054f
19#define SQRT_3          1.73205081f
20#define SQRT_3_OVER_2   0.8660254037844386f
21#define EULERS_CONSTANT 2.718281828f
22
23#define F1_TO_F3(x) float3((x), (x), (x))
24
25// Remaps [0, INT_MAX] to [0, 1]
26#define UINT_TO_UNIT (1.0 / 4294967296.0)
27
28float sin_noise_3d(float3 uvw) {
29  return sin(uvw[0]) * sin(uvw[1]) * sin(uvw[2]);
30}
31
32float sin_noise_3d_fbm(float3 uvw, uint octaves, float k, float strength) {
33  float result = 0;
34  float factor = 1.0f;
35  for (uint i = 0; i < octaves; i++) {
36    result += sin_noise_3d(uvw) * factor * strength;
37    uvw *= k;
38    factor /= k;
39  }
40  return result;
41}
42
43// Wrap NoL. Assume it's already clamped.
44// At k=0, you get standard lambertian shading.
45// At k=0.5, you get half-lambertian shading.
46// At k=1.0, you get flat shading.
47// k must be on [0, 1].
48// Energy preserving, within some small bound.
49float wrapNoL(float NoL, float k) {
50  float lambertian = NoL;
51  float tmp = (NoL + 0.5f) / (1.0f + 0.5f);
52  float half_lambertian = tmp * tmp;
53  float flat = RCP_PI;
54
55  if (k < 0.5) {
56    return lerp(lambertian, half_lambertian, k * 2.0f);
57  } else {
58    return lerp(half_lambertian, flat, k * 2.0f - 1.0f);
59  }
60}
61
62// Vector rotation with a quaternion
63// https://blog.molecular-matters.com/2013/05/24/a-faster-quaternion-vector-multiplication/
64float3 rotate_vector(float3 v, float4 q)
65{
66  float3 t = 2.0 * cross(q.xyz, v);
67  return v + q.w * t + cross(q.xyz, t);
68}
69
70float4 get_quaternion(float3 axis_normal, float theta) {
71  float sint2, cost2;
72  sincos(theta*0.5, sint2, cost2);
73  return float4(axis_normal * sint2, cost2);
74}
75
76// Cartesian to packed cube hexagonal coordinates.
77// Stores (q + r, q, r), where the article's cube coordinates satisfy
78// q + r + s = 0 and s = -(q + r).
79// Based on this: https://backdrifting.net/post/064_hex_grids
80float3 cart_to_hex(float2 cart) {
81  float q = cart.x - cart.y * RCP_SQRT_3;
82  float r = cart.y * (2.0f * RCP_SQRT_3);
83  return float3(q + r, q, r);
84}
85
86float2 hex_to_cart(float3 hex_coord) {
87  return float2(
88      hex_coord[1] + hex_coord[2] * 0.5f,
89      hex_coord[2] * SQRT_3_OVER_2);
90}
91
92
93float3 round_hex(float3 hex_coord) {
94    float3 rounded = round(hex_coord);
95    float3 diff = abs(rounded - hex_coord);
96    float error = rounded.x - rounded.y - rounded.z;
97
98    float3 max_mask = float3(
99        diff.x > diff.y && diff.x > diff.z,
100        diff.y > diff.x && diff.y > diff.z,
101        diff.z > diff.x && diff.z > diff.y);
102
103    rounded += error * float3(-1, 1, 1) * max_mask;
104    return rounded;
105}
106
107// Reoriented normal mapping
108// https://blog.selfshadow.com/publications/blending-in-detail/
109// Inputs are in tangent space.
110float3 blendNormalsHill12(float3 n0, float3 n1) {
111  n0.z += 1.0;
112  n1.xy = -n1.xy;
113  return normalize(n0 * dot(n0, n1) - n1 * n0.z);
114}
115
116// https://en.wikipedia.org/wiki/Relative_luminance
117float luminance(float3 rgb) {
118  return dot(float3(0.2126, 0.7152, 0.0722), rgb);
119}
120
121float4 alpha_blend(float4 front, float4 back) {
122  return float4(front.rgb * front.a + back.rgb * (1 - front.a), front.a + back.a * (1 - front.a));
123}
124
125// O'Neill-style PCG 32-bit output permutation (RXS-M-XS).
126uint pcg32(uint input) {
127  uint state = input * 747796405u + 2891336453u;
128  uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
129  return (word >> 22u) ^ word;
130}
131
132// 3 in 1 out hash. Mix three lanes in integer space, then PCG-finalize once.
133uint hash31_u32(uint3 p) {
134  uint seed = p.x * 0x2c1b3c6du;
135  seed ^= p.y * 0x297a2d39u;
136  seed ^= p.z * 0x1b56c4e9u;
137  return pcg32(seed);
138}
139
140// Float wrapper for arbitrary inputs.
141float hash31_ff(float3 p) {
142  return hash31_u32(asuint(p)) * UINT_TO_UNIT;
143}
144
145float hash31_if(int3 p) {
146  return hash31_u32(p) * UINT_TO_UNIT;
147}
148
149// Procedural value noise in [0,1]^3 — trilinear interpolation of hashed corners.
150float3 value_noise3(float3 p) {
151  int3 i = (int3)floor(p);
152  float3 f = frac(p);
153  float3 u = f * f * (3.0 - 2.0 * f);
154
155  return lerp(
156    lerp(lerp(hash31_if(i + int3(0, 0, 0)), hash31_if(i + int3(1, 0, 0)), u.x),
157         lerp(hash31_if(i + int3(0, 1, 0)), hash31_if(i + int3(1, 1, 0)), u.x), u.y),
158    lerp(lerp(hash31_if(i + int3(0, 0, 1)), hash31_if(i + int3(1, 0, 1)), u.x),
159         lerp(hash31_if(i + int3(0, 1, 1)), hash31_if(i + int3(1, 1, 1)), u.x), u.y),
160    u.z);
161}
162
163// Domain warping using procedural value noise.
164float3 domain_warp_procedural(float3 uvw, float strength,
165    uint octaves, float lacunarity, float gain) {
166  float3 noise = 0;
167  float g = 1;
168
169  for (uint ii = 0; ii < octaves; ++ii) {
170    // Need to remap noise onto [-1, 1] when domain warping.
171    float3 vnoise = value_noise3(uvw + (noise * 2.0 - 1.0) * strength);
172    noise += vnoise * g;
173    uvw *= lacunarity;
174    g *= gain;
175  }
176
177  noise *= (1 - gain) / (1 - pow(gain, octaves));
178
179  return noise;
180}
181
182float3 value_noise_3d_tex(Texture3D tex, SamplerState s, float3 p) {
183  float w, h, d;
184  tex.GetDimensions(w, h, d);
185  float3 res = float3(w, h, d);
186
187  p *= res;
188  float3 i = floor(p);
189  float3 f = frac(p);
190  float3 u = f * f * (3.0 - 2.0 * f);
191
192  return tex.Sample(s, (i + 0.5 + u) / res).rgb;
193}
194
195// Domain warping using a 3D noise texture. Texture should have an EV of
196// 0.5. Uses cubic interpolation between lattice points (same semantics as
197// domain_warp_procedural / value_noise3).
198float3 domain_warp_3d_tex(Texture3D noise_tex, SamplerState s, float3 uvw,
199    float strength, uint octaves, float lacunarity, float gain) {
200  float3 noise = 0;
201  float g = 1;
202
203  for (uint ii = 0; ii < octaves; ++ii) {
204    noise += value_noise_3d_tex(noise_tex, s, uvw + noise * strength) * g;
205    uvw *= lacunarity;
206    g *= gain;
207  }
208
209  // Normalize: geometric series 1 + r + ... + r^{n-1} = (1 - r^n) / (1 - r)
210  noise *= (1 - gain) / (1 - pow(gain, octaves));
211
212  return noise;
213}
214
215// Return distance to the nearest voronoi cell edge. Also returns p1 and p2,
216// vectors from the x to the nearest 2 lattice points, and p1_id, an identifier
217// for p1 which does not within the current cell.
218float voronoi_d_2d(float2 x, out float2 p1, out float2 p2, out float2 p1_id) {
219  float2 x_floor = floor(x);
220  float2 x_frac = frac(x);
221  float d1 = 1e6;
222  float d2 = 1e6;
223  p1 = 0;
224  p2 = 0;
225
226  for (int j = -1; j <= 1; j++) {
227    for (int i = -1; i <= 1; i++) {
228      float2 cell_offset = float2(i, j);
229      // Radial vector from origin lattice point to current.
230      float2 r = cell_offset + hash22_fast(x_floor + cell_offset) - x_frac;
231      float d = dot(r, r);
232      if (d < d1) {
233        d2 = d1;
234        p2 = p1;
235        d1 = d;
236        p1 = r;
237        p1_id = x_floor + cell_offset;
238      } else if (d < d2) {
239        d2 = d;
240        p2 = r;
241      }
242    }
243  }
244  return (d2 - d1);
245}
246
247// stripe_dir is the direction we want to draw stripes along. It must be
248// normalized.
249float4 phacelle_noise_2d(float2 x, float2 stripe_dir) {
250  float2 x_floor = floor(x);
251  float2 x_frac = frac(x);
252
253  float2 stripe_ortho = float2(-stripe_dir.y, stripe_dir.x) * TAU;
254
255  float2 vec = 0;
256  float normalization = 0;
257  for (int j = -1; j <= 2; j++) {
258    for (int i = -1; i <= 2; i++) {
259      float2 cell_offset = float2(i, j);
260      // Radial vector from origin lattice point to current.
261      float2 r = cell_offset + hash22_fast(x_floor + cell_offset) - 0.5 - x_frac;
262      float d2 = dot(r, r);
263      // 0th cell is centered at 0.5.
264      float weight = max(0, exp(-2.0 * d2) - 0.01111);
265      normalization += weight;
266      float wave_input = -dot(r, stripe_ortho);
267      float vecs, vecc;
268      sincos(wave_input, vecs, vecc);
269      vec += float2(vecc, vecs) * weight;
270    }
271  }
272  vec /= normalization;
273  float magnitude = sqrt(dot(vec, vec));
274  return float4(vec / magnitude, stripe_ortho);
275}
276
277float voronoi_d_2d(float2 x) {
278  float2 p1, p2, p1_id;
279  return voronoi_d_2d(x, p1, p2, p1_id);
280}
281
282// Return distance to the nearest voronoi cell edge, clamped to [0, 0.5].
283// 0.5 is on the edge, 0 is far from it.
284float voronoi_d_3d(float3 x) {
285  float3 x_floor = floor(x);
286  float3 x_frac = frac(x);
287  float d1 = 1e6;
288  float d2 = 1e6;
289  float3 p1 = 0;
290  float3 p2 = 0;
291
292  for (int k = -1; k <= 1; k++) {
293    for (int j = -1; j <= 1; j++) {
294      for (int i = -1; i <= 1; i++) {
295        float3 cell_offset = float3(i, j, k);
296        float3 r = cell_offset + hash33_fast(x_floor + cell_offset) - x_frac;
297        float d = dot(r, r);
298        if (d < d1) {
299          d2 = d1;
300          p2 = p1;
301          d1 = d;
302          p1 = r;
303        } else if (d < d2) {
304          d2 = d;
305          p2 = r;
306        }
307      }
308    }
309  }
310  float d = (d2 - d1) / (2.0f * max(1e-4, length(p2 - p1)));
311  return max(0.0f, 0.5f - d);
312}
313
314float median(float3 x) {
315  // Get the min and max.
316  float x_min= min(min(x.r, x.g), x.b);
317  float x_max = max(max(x.r, x.g), x.b);
318
319  // Compute (x.r + x.g + x.b) - (x_min + x_max). This gives us the median.
320  return (x.r + x.g + x.b) - (x_min + x_max);
321}
322
323float3 linear_to_srgb(float3 linear_color) {
324  float3 lo = 12.92f * linear_color;
325  float3 hi = 1.055f * pow(linear_color, 1.0f / 2.4f) - 0.055f;
326  return lerp(lo, hi, step(0.0031308f, linear_color));
327}
328
329float3 srgb_to_linear(float3 srgb_color) {
330  float3 lo = srgb_color / 12.92f;
331  float3 hi = pow((srgb_color + 0.055f) / 1.055f, 2.4f);
332  return lerp(lo, hi, step(0.04045f, srgb_color));
333}
334
335float3x3 tbn_from_normal_tangent(float3 normal, float4 tangent) {
336  float3 n = normal;
337  float3 t = tangent.xyz;
338  t = normalize(t - n * dot(n, t));  // Gram-Schmidt to avoid skew
339  float3 b = normalize(cross(n, t)) * tangent.w;
340  return float3x3(t, b, n);
341}
342
343#endif  // __MATH_INC