yum-archive/2ner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum-archive/2ner
f478606
master
1#ifndef LTCGI_FUNCTIONS_INCLUDED 2#define LTCGI_FUNCTIONS_INCLUDED 3 4/* 5 LTC HELPERS 6*/ 7 8float3 LTCGI_IntegrateEdge(float3 v1, float3 v2) 9{ 10 float x = dot(v1, v2); 11 float y = abs(x); 12 13 float a = 0.8543985 + (0.4965155 + 0.0145206*y)*y; 14 float b = 3.4175940 + (4.1616724 + y)*y; 15 float v = a / b; 16 float theta_sintheta = (x > 0.0) ? v : 0.5*rsqrt(max(1.0 - x*x, 1e-7)) - v; 17 18 return cross(v1, v2) * theta_sintheta; 19} 20 21void LTCGI_ClipQuadToHorizon(inout float3 L[5], out int n) 22{ 23 // detect clipping config 24 uint config = 0; 25 if (L[0].z > 0.0) config += 1; 26 if (L[1].z > 0.0) config += 2; 27 if (L[2].z > 0.0) config += 4; 28 if (L[3].z > 0.0) config += 8; 29 30 n = 0; 31 32 // This [forcecase] only works when the cases are ordered in a specific manner. 33 // It gives like 10%-20% performance boost, so *make sure to leave it on*! 34 // If it breaks however, see if [branch] fixes it, and if it does, start 35 // reordering cases at random until it works again. 36 // It seems the compiler somehow optimizes away anything but setting 'n' in 37 // some orderings, including the ascending and descending ones. 38 // I wish I was joking. 39 [forcecase] 40 switch (config) { 41 case 13: // V1 V3 V4 clip V2 <- tl;dr: this fecker has to be first or shader go boom 42 n = 5; 43 L[4] = L[3]; 44 L[3] = L[2]; 45 L[2] = -L[1].z * L[2] + L[2].z * L[1]; 46 L[1] = -L[1].z * L[0] + L[0].z * L[1]; 47 break; 48 case 15: // V1 V2 V3 V4 - most common 49 n = 4; 50 break; 51 case 9: // V1 V4 clip V2 V3 52 n = 4; 53 L[1] = -L[1].z * L[0] + L[0].z * L[1]; 54 L[2] = -L[2].z * L[3] + L[3].z * L[2]; 55 break; 56 case 0: // clip all 57 break; 58 case 1: // V1 clip V2 V3 V4 59 n = 3; 60 L[1] = -L[1].z * L[0] + L[0].z * L[1]; 61 L[2] = -L[3].z * L[0] + L[0].z * L[3]; 62 L[3] = L[0]; 63 break; 64 case 2: // V2 clip V1 V3 V4 65 n = 3; 66 L[0] = -L[0].z * L[1] + L[1].z * L[0]; 67 L[2] = -L[2].z * L[1] + L[1].z * L[2]; 68 L[3] = L[0]; 69 break; 70 case 3: // V1 V2 clip V3 V4 71 n = 4; 72 L[2] = -L[2].z * L[1] + L[1].z * L[2]; 73 L[3] = -L[3].z * L[0] + L[0].z * L[3]; 74 break; 75 case 4: // V3 clip V1 V2 V4 76 n = 3; 77 L[0] = -L[3].z * L[2] + L[2].z * L[3]; 78 L[1] = -L[1].z * L[2] + L[2].z * L[1]; 79 L[3] = L[0]; 80 break; 81 case 5: // V1 V3 clip V2 V4) impossible 82 break; 83 case 6: // V2 V3 clip V1 V4 84 n = 4; 85 L[0] = -L[0].z * L[1] + L[1].z * L[0]; 86 L[3] = -L[3].z * L[2] + L[2].z * L[3]; 87 break; 88 case 7: // V1 V2 V3 clip V4 89 n = 5; 90 L[4] = -L[3].z * L[0] + L[0].z * L[3]; 91 L[3] = -L[3].z * L[2] + L[2].z * L[3]; 92 break; 93 case 8: // V4 clip V1 V2 V3 94 n = 3; 95 L[0] = -L[0].z * L[3] + L[3].z * L[0]; 96 L[1] = -L[2].z * L[3] + L[3].z * L[2]; 97 L[2] = L[3]; 98 break; 99 case 10: // V2 V4 clip V1 V3) impossible 100 break; 101 case 11: // V1 V2 V4 clip V3 102 n = 5; 103 L[4] = L[3]; 104 L[3] = -L[2].z * L[3] + L[3].z * L[2]; 105 L[2] = -L[2].z * L[1] + L[1].z * L[2]; 106 break; 107 case 12: // V3 V4 clip V1 V2 108 n = 4; 109 L[1] = -L[1].z * L[2] + L[2].z * L[1]; 110 L[0] = -L[0].z * L[3] + L[3].z * L[0]; 111 break; 112 case 14: // V2 V3 V4 clip V1 113 n = 5; 114 L[4] = -L[0].z * L[3] + L[3].z * L[0]; 115 L[0] = -L[0].z * L[1] + L[1].z * L[0]; 116 break; 117 } 118 119 // inlining these branches *unconditionally* breaks the [forcecase] above 120 // ...yeah I know 121 if (n == 3) 122 L[3] = L[0]; 123 if (n == 4) 124 L[4] = L[0]; 125} 126 127/* 128 TEXTURE SAMPLING 129*/ 130 131float2 LTCGI_inset_uv(float2 uv) 132{ 133 return uv * 0.75 + float2(0.125, 0.125); 134} 135 136half3 premul_alpha(half4 i) 137{ 138 return i.rgb * i.a; 139} 140 141half max2(half2 v) 142{ 143 return max(v.x, v.y); 144} 145 146void LTCGI_sample(float2 uv, uint lod, uint idx, float blend, out float3 result) 147{ 148#ifndef LTCGI_STATIC_TEXTURES 149 idx = 0; // optimize away the branches below 150#endif 151 152#ifdef LTCGI_FAST_SAMPLING 153 #ifndef SHADER_TARGET_SURFACE_ANALYSIS 154 float outside = max2(abs(uv - 0.5f) - 0.5f); 155 float outmod = smoothstep(-0.1f, 0.1f, outside) * 2.5f; 156 blend = blend * 2.5f + outmod; 157 [branch] 158 if (idx == 0) 159 { 160 result = premul_alpha(_Udon_LTCGI_Texture_LOD0.SampleLevel(LTCGI_SAMPLER, uv, blend)); 161 } 162 #ifdef LTCGI_STATIC_TEXTURES 163 else 164 { 165 result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD( 166 _Udon_LTCGI_Texture_LOD0_arr, 167 LTCGI_SAMPLER_RAW, 168 float3(uv, idx - 1), 169 blend 170 ).rgb; 171 } 172 #endif 173 #else 174 result = 0; 175 #endif 176#else 177 result = 0; 178 179 [branch] 180 if (lod == 0) 181 { 182 // if we're outside of the 0-1 UV space we must sample a prefiltered texture 183 [branch] 184 if(any(saturate(abs(uv - 0.5) - 0.5))) 185 { 186 lod = 1; 187 } 188 else 189 { 190 // LOD0 is the original texture itself, so not prefiltered, but we can 191 // approximate it a bit with trilinear lod 192 float lod = (1 - blend) * 1.5; 193 [branch] 194 if (idx == 0) 195 { 196 #ifndef SHADER_TARGET_SURFACE_ANALYSIS 197 result = premul_alpha(_Udon_LTCGI_Texture_LOD0.SampleLevel(LTCGI_SAMPLER, uv, lod)); 198 return; 199 #else 200 result = 0; 201 return; 202 #endif 203 } 204 #ifdef LTCGI_STATIC_TEXTURES 205 else 206 { 207 result = premul_alpha(UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD( 208 _Udon_LTCGI_Texture_LOD0_arr, 209 LTCGI_SAMPLER_RAW, 210 float3(uv, idx - 1), 211 lod 212 )); 213 return; 214 } 215 #endif 216 } 217 } 218 219 float2 ruv = LTCGI_inset_uv(uv); 220 221 [branch] 222 if (idx == 0) 223 { 224 #ifndef SHADER_TARGET_SURFACE_ANALYSIS 225 switch (lod) 226 { 227 case 1: 228 result = _Udon_LTCGI_Texture_LOD1.SampleLevel(LTCGI_SAMPLER, ruv, 0).rgb; 229 return; 230 case 2: 231 result = _Udon_LTCGI_Texture_LOD2.SampleLevel(LTCGI_SAMPLER, ruv, 0).rgb; 232 return; 233 default: 234 result = _Udon_LTCGI_Texture_LOD3.SampleLevel(LTCGI_SAMPLER, ruv, blend*0.72).rgb; 235 return; 236 } 237 #else 238 result = 0; 239 return; 240 #endif 241 } 242 #ifdef LTCGI_STATIC_TEXTURES 243 else 244 { 245 [forcecase] 246 switch (lod) 247 { 248 case 1: 249 result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD( 250 _Udon_LTCGI_Texture_LOD1_arr, 251 LTCGI_SAMPLER_RAW, 252 float3(ruv, idx - 1), 253 0 254 ).rgb; 255 return; 256 case 2: 257 result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD( 258 _Udon_LTCGI_Texture_LOD2_arr, 259 LTCGI_SAMPLER_RAW, 260 float3(ruv, idx - 1), 261 0 262 ).rgb; 263 return; 264 default: 265 result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD( 266 _Udon_LTCGI_Texture_LOD3_arr, 267 LTCGI_SAMPLER_RAW, 268 float3(ruv, idx - 1), 269 blend 270 ).rgb; 271 return; 272 } 273 } 274 #endif 275#endif 276} 277 278void LTCGI_trilinear(float2 uv, float d, uint idx, out float3 result) 279{ 280#ifdef LTCGI_FAST_SAMPLING 281 LTCGI_sample(uv, 0, idx, d, result); 282#else 283 uint low = (uint)d; 284 uint high = low + 1; 285 286 // DEBUG: colorize d/lod 287 //return float3(low == 0, low == 1, low == 2); 288 289 if (low >= 3) 290 { 291 LTCGI_sample(uv, 3, idx, d - 3, result); 292 } 293 else 294 { 295 float amount = saturate(high - d); 296 float3 low_sample; 297 LTCGI_sample(uv, low, idx, amount, low_sample); 298 float3 high_sample; 299 LTCGI_sample(uv, high, idx, 0, high_sample); 300 301 result = lerp(high_sample, low_sample, amount); 302 } 303#endif 304} 305 306/* 307 GENERIC HELPERS 308*/ 309 310// from: https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/ 311// max absolute error 9.0x10^-3 312// Eberly's polynomial degree 1 - respect bounds 313// 4 VGPR, 12 FR (8 FR, 1 QR), 1 scalar 314// input [-1, 1] and output [0, PI] 315float LTCGI_acos_fast(float inX) 316{ 317 float x = abs(inX); 318 float res = -0.156583f * x + UNITY_HALF_PI; 319 res *= sqrt(1.0f - x); 320 return (inX >= 0) ? res : UNITY_PI - res; 321} 322 323bool LTCGI_tri_ray(float3 orig, float3 dir, float3 v0, float3 v1, float3 v2, out float2 bary) { 324 float3 v0v1 = v1 - v0; 325 float3 v0v2 = v2 - v0; 326 float3 pvec = cross(dir, v0v2); 327 float det = dot(v0v1, pvec); 328 float invDet = 1 / det; 329 330 float3 tvec = orig - v0; 331 bary.x = dot(tvec, pvec) * invDet; 332 333 float3 qvec = cross(tvec, v0v1); 334 bary.y = dot(dir, qvec) * invDet; 335 336 // return false when other triangle of quad should be sampled, 337 // i.e. we went over the diagonal line 338 return bary.x >= 0; 339} 340 341float2 LTCGI_rotateVector(float2 x, float angle) 342{ 343 float c = cos(angle); 344 float s = sin(angle); 345 return mul(float2x2(c,s,-s,c), x); 346} 347 348/*float LTCGI_remap(float3 from, float3 to, float2 targetFrom, float2 targetTo, float3 value) 349{ 350 float rel = (value - from) / (to - from); 351 return lerp(targetFrom, targetTo, rel); 352}*/ 353 354float2 LTCGI_calculateUV(uint i, ltcgi_flags flags, float3 L[5], bool isTri, float4 uvStart, float4 uvEnd, out float3 ray) 355{ 356 // calculate perpendicular vector to plane defined by area light 357 float3 E1 = L[1] - L[0]; 358 float3 E2 = L[3] - L[0]; 359 ray = cross(E1, E2); 360 361 // raycast it against the two triangles formed by the quad 362 float2 bary; 363 bool hit0 = LTCGI_tri_ray(0, ray, L[0], L[2], L[3], bary) || isTri; 364 if (!hit0) { 365 LTCGI_tri_ray(0, ray, L[0], L[1], L[2], bary); 366 } 367 368 float3 bary3 = float3(bary, 1 - bary.x - bary.y); 369 float2 uv; 370 if (hit0) 371 uv = uvEnd.zw * bary3.x + uvEnd.xy * bary3.y; 372 else 373 uv = uvStart.zw * bary3.x + uvEnd.zw * bary3.y; 374 return uv + uvStart.xy * bary3.z; 375} 376 377/* 378 EXPERIMENTAL: CYLINDER HELPER 379*/ 380 381void LTCGI_GetLw(uint i, ltcgi_flags flags, float3 worldPos, out float3 Lw[4], out float4 uvStart, out float4 uvEnd, out bool isTri) { 382 bool cylinder = false; 383 #ifdef LTCGI_CYLINDER 384 // statically optimize out branch below in case disabled 385 cylinder = flags.cylinder; 386 #endif 387 388 float4 v0 = _Udon_LTCGI_Vertices_0_get(i); 389 float4 v1 = _Udon_LTCGI_Vertices_1_get(i); 390 float4 v2 = _Udon_LTCGI_Vertices_2_get(i); 391 float4 v3 = _Udon_LTCGI_Vertices_3_get(i); 392 393 [branch] 394 if (cylinder) { 395 // construct data according to worldPos to create aligned 396 // rectangle tangent to the cylinder 397 398 float3 in_base = v0.xyz; 399 float in_height = v0.w; 400 float in_radius = v1.w; 401 float in_size = v2.w; 402 float in_angle = v3.w; 403 404 // get angle between 2D unit plane and vector pointing from cylinder to shade point 405 float2 towardsCylinder = LTCGI_rotateVector((in_base - worldPos).xz, -in_angle); 406 float angle = atan2(towardsCylinder.x, towardsCylinder.y); 407 // clamp angle to size parameter, i.e. "width" of lit surface area 408 float angleClamped = clamp(angle, -in_size, in_size) + in_angle; 409 // construct vector that *most* faces shade point 410 float2 facing = float2(sin(angleClamped), cos(angleClamped)); 411 // tangent of rectangular screen on cylinder surface used for calculating lighting for shade point 412 float2 tangent = float2(facing.y, -facing.x); 413 float2 onCylinderFacing = facing * in_radius; 414 415 // clip ends, approximately 416 float rclip = saturate(lerp(1, 0, (angleClamped - in_angle) - (in_size - UNITY_HALF_PI*0.5f))); 417 float lclip = saturate(lerp(1, 0, -(angleClamped - in_angle) - (in_size - UNITY_HALF_PI*0.5f))); 418 419 float2 p1 = in_base.xz - onCylinderFacing + tangent * in_radius * lclip; 420 float2 p2 = in_base.xz - onCylinderFacing - tangent * in_radius * rclip; 421 422 Lw[0] = float3(p1.x, in_base.y, p1.y) - worldPos; 423 Lw[1] = float3(p1.x, in_base.y + in_height, p1.y) - worldPos; 424 Lw[2] = float3(p2.x, in_base.y, p2.y) - worldPos; 425 Lw[3] = float3(p2.x, in_base.y + in_height, p2.y) - worldPos; 426 427 isTri = false; 428 429 // UV depends on "viewing" angle of the shade point towards the cylinder 430 float2 viewDir = normalize((in_base - worldPos).xz); 431 // forwardAngle == atan2(cos(in_angle), sin(in_angle)); but only negative 432 float forwardAngle = -in_angle + UNITY_HALF_PI; 433 // offset from center of screen forward to the side ends, positive goes left/ccw fpv top, 434 // sine to account for the fact we're rotating around a cylinder which has depth 435 float viewAngle = forwardAngle - atan2(viewDir.y, viewDir.x); 436 // prevent rollover, since we need to clamp we must stay withing [-pi, pi] 437 if (viewAngle < -UNITY_PI) 438 viewAngle += UNITY_TWO_PI; 439 if (viewAngle > UNITY_PI) 440 viewAngle -= UNITY_TWO_PI; 441 viewAngle = clamp(viewAngle * 0.5f, -in_size, in_size); 442 viewAngle = sin(viewAngle); 443 // full view UVs, but shifted left/right depending on view angle 444 float2 uvStart2 = float2(1 - saturate(viewAngle), 0); 445 float2 uvEnd2 = float2(1 - saturate(viewAngle + 1), 1); 446 uvStart = float4(uvStart2.x, uvStart2.y, uvStart2.x, uvEnd2.y); 447 uvEnd = float4(uvEnd2.x, uvStart2.y, uvEnd2.x, uvEnd2.y); 448 449 } else { 450 // use passed in data, offset around worldPos 451 Lw[0] = v0.xyz - worldPos; 452 Lw[1] = v1.xyz - worldPos; 453 Lw[2] = v2.xyz - worldPos; 454 Lw[3] = v3.xyz - worldPos; 455 #ifndef SHADER_TARGET_SURFACE_ANALYSIS 456 uvStart = _Udon_LTCGI_static_uniforms[uint2(4, i)]; 457 uvEnd = _Udon_LTCGI_static_uniforms[uint2(5, i)]; 458 #else 459 uvStart = float4(0, 0, 1, 0); 460 uvEnd = float4(1, 1, 0, 1); 461 #endif 462 463 // we only detect triangles for "blender" import configuration, as those are the only 464 // ones that can actually be triangles (I think?) 465 isTri = /*distance(Lw[2], Lw[3]) < 0.001 || */distance(Lw[1], Lw[3]) < 0.001; 466 } 467} 468 469#endif 470 471/* 472 473Parts of the code in this file are adapted from the example code found here: 474 475 https://github.com/selfshadow/ltc_code 476 477Modifications by _pi_ (@pimaker on GitHub), licensed under the terms of the 478MIT license as far as applicable. 479 480Original copyright notice: 481 482Copyright (c) 2017, Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt. 483All rights reserved. 484 485Redistribution and use in source and binary forms, with or without 486modification, are permitted provided that the following conditions are met: 487 488* If you use (or adapt) the source code in your own work, please include a 489 reference to the paper: 490 491 Real-Time Polygonal-Light Shading with Linearly Transformed Cosines. 492 Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt. 493 ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016. 494 Project page: https://eheitzresearch.wordpress.com/415-2/ 495 496* Redistributions of source code must retain the above copyright notice, this 497 list of conditions and the following disclaimer. 498 499* Redistributions in binary form must reproduce the above copyright notice, 500 this list of conditions and the following disclaimer in the documentation 501 and/or other materials provided with the distribution. 502 503THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 504AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 505IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 506DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 507FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 508DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 509SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 510CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 511OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 512OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 513 514*/