yum/3ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum/3ner
ecd59e8
master
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