yum-archive/Tooner
A toon shader for Unity's BIRP.
git clone https://git.yummers.dev/yum-archive/Tooner
ccbf413
master
1#include "UnityCG.cginc" 2#include "AutoLight.cginc" 3#include "UnityPBSLighting.cginc" 4 5#include "audiolink.cginc" 6#include "clones.cginc" 7#include "cnlohr.cginc" 8#include "gerstner.cginc" 9#include "globals.cginc" 10#include "interpolators.cginc" 11#include "math.cginc" 12#include "pbr.cginc" 13#include "oklab.cginc" 14#include "trochoid_math.cginc" 15#include "tooner_scroll.cginc" 16#include "UnityCG.cginc" 17 18#ifndef __TOONER_OUTLINE_PASS 19#define __TOONER_OUTLINE_PASS 20 21#define _OUTLINE_INTERPOLATORS 22 23v2f vert(appdata v) 24{ 25#if defined(_DISCARD) 26 if (_Discard_Enable_Dynamic) { 27 return (v2f) (0.0 / 0.0); 28 } 29#endif 30 v2f o; 31 32 UNITY_INITIALIZE_OUTPUT(v2f, o); 33 UNITY_SETUP_INSTANCE_ID(v); 34 UNITY_TRANSFER_INSTANCE_ID(v, o); 35 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); 36 37#if defined(_TROCHOID) 38 { 39 v.vertex.xyz = cyl2_to_troch_map(cyl_to_cyl2_map(cart_to_cyl_map(v.vertex.xyz))); 40 } 41#endif 42 43 float4 objPos = v.vertex; 44 45#if defined(_OUTLINES) 46#if !defined(_SCROLL) && defined(_GIMMICK_GERSTNER_WATER) 47 { 48 GerstnerParams p = getGerstnerParams(); 49 objPos.xyz = gerstner_vert(objPos.xyz, p); 50 GerstnerFragResult r = gerstner_frag(objPos.xyz, p); 51 v.normal = r.normal; 52 } 53#endif 54#endif 55 56#if defined(_OUTLINES) 57 float outline_mask = _Outline_Mask.SampleLevel(linear_repeat_s, v.uv0.xy, /*lod=*/0); 58 outline_mask = _Outline_Mask_Invert > 1E-9 ? 1 - outline_mask : outline_mask; 59 60 objPos.xyz += v.normal * _Outline_Width * outline_mask * _Outline_Width_Multiplier; 61#endif 62 63#if defined(_FACE_ME_WORLD_Y) 64 [branch] 65 if (_FaceMeWorldY_Enable_Dynamic) { 66 float3 object_center = mul(unity_ObjectToWorld, float4(0, 0, 0, 1)); 67 // Get forward axis of object coordinate system, i.e. the orientation of 68 // the hip bone. 69 // Then project it onto the xz plane. 70 float3 forward_axis = mul(unity_ObjectToWorld, float3(0, 0, 1)); 71 forward_axis.y = 0; 72 forward_axis = normalize(forward_axis); 73 float4 worldPos = mul(unity_ObjectToWorld, objPos); 74 float3 rd = normalize((worldPos.xyz - object_center) - getCenterCamPos()); 75 // We apply a factor of -1 to shift the result forward by a phase shift of pi. 76 float cos_t = -dot(normalize(rd.xz), forward_axis.xz); 77 // We want to get sin(t) using the identity: 78 // || a x b || = || a || || b || sin(t) 79 // For normal vectors, this simplifies to: 80 // || a x b || = sin(t) 81 // The issue is that the norm operator loses the sign. 82 // We can estimate the sign by assuming that `rd` and `forward_axis` are on 83 // the xz plane. 84 // If that's the case, then the cross product is necessarily constrained to 85 // the y axis. 86 float sin_t_sign = sign(cross(rd, forward_axis).y); 87 // Here we use the identity: 88 // sin(t) = sqrt(1 - cos(t)^2) 89 // We simply apply the sign correction `sin_t_sign` to the result. 90 // We then invert it, since the goal is not to amplify the rotation, but 91 // to negate it. 92 // Finally, we add a phase correction to make the abomination face us. 93 float sin_t = -sqrt(1 - cos_t * cos_t) * sin_t_sign; 94 float2x2 face_me_rot = float2x2(cos_t, -sin_t, sin_t, cos_t); 95 float2x2 face_me_rot_inv = float2x2(cos_t, sin_t, -sin_t, cos_t); 96 worldPos.xz = mul(face_me_rot, (worldPos.xz - object_center.xz)) + object_center.xz; 97 objPos = mul(unity_WorldToObject, worldPos); 98 float3 world_normal = UnityObjectToWorldNormal(v.normal); 99 world_normal.xz = mul(face_me_rot_inv, world_normal.xz); 100 v.normal = normalize(mul(unity_WorldToObject, world_normal)); 101 } 102#endif 103 104#if !defined(_SCROLL) && defined(_GIMMICK_SPHERIZE_LOCATION) 105 if (_Gimmick_Spherize_Location_Enable_Dynamic) { 106 float r = _Gimmick_Spherize_Location_Radius; 107 float s = _Gimmick_Spherize_Location_Strength; 108 float l = length(objPos.xyz); 109 objPos.xyz *= lerp(1, (r / l), s); 110 } 111#endif 112#if !defined(_SCROLL) && defined(_GIMMICK_SHEAR_LOCATION) 113 if (_Gimmick_Shear_Location_Enable_Dynamic) { 114 objPos = mul(float4x4( 115 _Gimmick_Shear_Location_Strength.x, 0, 0, 0, 116 0, _Gimmick_Shear_Location_Strength.y, 0, 0, 117 0, 0, _Gimmick_Shear_Location_Strength.z, 0, 118 0, 0, 0, _Gimmick_Shear_Location_Strength.w), 119 objPos); 120 } 121#endif 122 123 o.worldPos = mul(unity_ObjectToWorld, objPos); 124 o.objPos = objPos; 125 o.pos = UnityObjectToClipPos(objPos); 126 o.normal = UnityObjectToWorldNormal(v.normal); 127 o.uv0 = v.uv0; 128#if !defined(_OPTIMIZE_INTERPOLATORS) 129 o.uv1 = v.uv1; 130#if defined(LIGHTMAP_ON) 131 o.uv2 = v.uv2 * unity_LightmapST.xy + unity_LightmapST.zw; 132 UNITY_TRANSFER_LIGHTING(o, v.uv1); 133#else 134 o.uv2 = v.uv2; 135 o.uv3 = v.uv3; 136 o.uv4 = v.uv4; 137 o.uv5 = v.uv5; 138 o.uv6 = v.uv6; 139 o.uv7 = v.uv7; 140#endif 141#endif // _OPTIMIZE_INTERPOLATORS 142 143 o.centerCamPos = getCenterCamPos(); 144 return o; 145} 146 147// maxvertexcount == the number of vertices we create 148#if defined(_CLONES) 149[maxvertexcount(15)] 150#else 151[maxvertexcount(3)] 152#endif 153void geom(triangle v2f tri_in[3], 154 uint pid: SV_PrimitiveID, 155 inout TriangleStream<v2f> tri_out) 156{ 157 v2f v0 = tri_in[0]; 158 v2f v1 = tri_in[1]; 159 v2f v2 = tri_in[2]; 160 161 float3 v0_objPos; 162 float3 v1_objPos; 163 float3 v2_objPos; 164 165 const float pid_rand = rand((int) pid); 166 float explode_phase = 0; 167#if defined(_EXPLODE) 168 float3 n = normalize(cross(v1.worldPos - v0.worldPos, v2.worldPos - v0.worldPos)); 169 float3 avg_pos; 170 171 float3 n0 = v0.normal; 172 float3 n1 = v1.normal; 173 float3 n2 = v2.normal; 174 175 explode_phase = _Explode_Phase; 176 explode_phase = smoothstep(0, 1, explode_phase); 177 explode_phase *= explode_phase; 178 explode_phase *= 4; 179 180 if (explode_phase > 1E-6) { 181 float3 axis = normalize(float3( 182 rand((int) ((v0.uv0.x + v0.uv0.y) * 1E9)) * 2 - 1, 183 rand((int) ((v1.uv0.x + v1.uv0.y) * 1E9)) * 2 - 1, 184 rand((int) ((v2.uv0.x + v2.uv0.y) * 1E9)) * 2 - 1)); 185 float3 np = BlendNormals(n, axis * explode_phase); 186 187 v0.worldPos += np * explode_phase * pid_rand; 188 v1.worldPos += np * explode_phase * pid_rand; 189 v2.worldPos += np * explode_phase * pid_rand; 190 191 v0_objPos = mul(unity_WorldToObject, float4(v0.worldPos, 1)); 192 v1_objPos = mul(unity_WorldToObject, float4(v1.worldPos, 1)); 193 v2_objPos = mul(unity_WorldToObject, float4(v2.worldPos, 1)); 194 195 float chrono = 0; 196#if defined(_AUDIOLINK) 197 if (AudioLinkIsAvailable()) { 198 chrono = (AudioLinkDecodeDataAsUInt( ALPASS_CHRONOTENSITY + uint2( 2, 1 ) ) % 1000000) / 1000000.0; 199 } 200#endif 201 v0.worldPos += n * explode_phase * sin(_Time[2] + length(v0_objPos)*6 + chrono) * .01 + chrono * n * explode_phase * .2; 202 v1.worldPos += n * explode_phase * sin(_Time[2] + length(v1_objPos)*6 + chrono) * .01 + chrono * n * explode_phase * .2; 203 v2.worldPos += n * explode_phase * sin(_Time[2] + length(v2_objPos)*6 + chrono) * .01 + chrono * n * explode_phase * .2; 204 205 avg_pos = (v0.worldPos + v1.worldPos + v2.worldPos) / 3; 206 v0.worldPos -= avg_pos; 207 v1.worldPos -= avg_pos; 208 v2.worldPos -= avg_pos; 209 210 float theta = explode_phase * 3.14159 * 4 + explode_phase * (sin(_Time[1] * (1 + pid_rand) / 2.0 + pid_rand) + cos(_Time[1] * (1 + pid_rand) / 6.1 + pid_rand) * 2) * pid_rand * 2; 211 float4 quat = get_quaternion(axis, theta); 212 v0.worldPos = rotate_vector(v0.worldPos, quat); 213 v1.worldPos = rotate_vector(v1.worldPos, quat); 214 v2.worldPos = rotate_vector(v2.worldPos, quat); 215 216 v0.worldPos += avg_pos; 217 v1.worldPos += avg_pos; 218 v2.worldPos += avg_pos; 219 220 n = normalize(cross(v1.worldPos - v0.worldPos, v2.worldPos - v0.worldPos)); 221 v0.normal = n; 222 v1.normal = n; 223 v2.normal = n; 224 225 // Omit geometry that's too close when exploded. 226 /* 227 if (_Explode_Phase > .05 && length(v0.worldPos - _WorldSpaceCameraPos) < .2) { 228 return; 229 } 230 */ 231 232 v0_objPos = mul(unity_WorldToObject, float4(v0.worldPos, 1)); 233 v1_objPos = mul(unity_WorldToObject, float4(v1.worldPos, 1)); 234 v2_objPos = mul(unity_WorldToObject, float4(v2.worldPos, 1)); 235 236 // Apply transformed worldPos to other coordinate systems. 237 if (_Explode_Phase > 1E-6) { 238 v0.pos = UnityObjectToClipPos(v0_objPos); 239 v1.pos = UnityObjectToClipPos(v1_objPos); 240 v2.pos = UnityObjectToClipPos(v2_objPos); 241 } 242 } 243#endif // __EXPLODE 244#if defined(_SCROLL) 245 { 246 float3 n = normalize(cross(v1.worldPos - v0.worldPos, v2.worldPos - v0.worldPos)); 247 float3 avg_pos = (v0.worldPos + v1.worldPos + v2.worldPos) / 3; 248 v0.worldPos = applyScroll(v0.worldPos, n, avg_pos); 249 v1.worldPos = applyScroll(v1.worldPos, n, avg_pos); 250 v2.worldPos = applyScroll(v2.worldPos, n, avg_pos); 251 252 float3 v0_objPos = mul(unity_WorldToObject, float4(v0.worldPos, 1)); 253 float3 v1_objPos = mul(unity_WorldToObject, float4(v1.worldPos, 1)); 254 float3 v2_objPos = mul(unity_WorldToObject, float4(v2.worldPos, 1)); 255 256#if defined(_GIMMICK_SPHERIZE_LOCATION) 257 if (_Gimmick_Spherize_Location_Enable_Dynamic) { 258 float r = _Gimmick_Spherize_Location_Radius; 259 float s = _Gimmick_Spherize_Location_Strength; 260 float l0 = length(v0_objPos); 261 float l1 = length(v1_objPos); 262 float l2 = length(v2_objPos); 263 v0_objPos *= lerp(1, (r / l0), s); 264 v1_objPos *= lerp(1, (r / l1), s); 265 v2_objPos *= lerp(1, (r / l2), s); 266 } 267#endif 268#if defined(_GIMMICK_SHEAR_LOCATION) 269 if (_Gimmick_Shear_Location_Enable_Dynamic) { 270 v0_objPos = mul(float3x3( 271 _Gimmick_Shear_Location_Strength.x, 0, 0, 272 0, _Gimmick_Shear_Location_Strength.y, 0, 273 0, 0, _Gimmick_Shear_Location_Strength.z), 274 v0_objPos); 275 v1_objPos = mul(float3x3( 276 _Gimmick_Shear_Location_Strength.x, 0, 0, 277 0, _Gimmick_Shear_Location_Strength.y, 0, 278 0, 0, _Gimmick_Shear_Location_Strength.z), 279 v1_objPos); 280 v2_objPos = mul(float3x3( 281 _Gimmick_Shear_Location_Strength.x, 0, 0, 282 0, _Gimmick_Shear_Location_Strength.y, 0, 283 0, 0, _Gimmick_Shear_Location_Strength.z), 284 v2_objPos); 285 } 286#endif 287#if defined(_GIMMICK_SHEAR_LOCATION) || defined(_GIMMICK_SPHERIZE_LOCATION) 288 v0.worldPos.xyz = mul(unity_ObjectToWorld, v0_objPos); 289 v1.worldPos.xyz = mul(unity_ObjectToWorld, v1_objPos); 290 v2.worldPos.xyz = mul(unity_ObjectToWorld, v2_objPos); 291#endif 292 293 v0.pos = UnityObjectToClipPos(v0_objPos); 294 v1.pos = UnityObjectToClipPos(v1_objPos); 295 v2.pos = UnityObjectToClipPos(v2_objPos); 296 } 297#endif 298#if defined(_CLONES) 299 v2f clone_verts[3] = {v0, v1, v2}; 300 add_clones(clone_verts, tri_out, pid_rand, explode_phase); 301#endif // _CLONES 302 303 // Output transformed geometry. 304 tri_out.Append(v0); 305 tri_out.Append(v1); 306 tri_out.Append(v2); 307 tri_out.RestartStrip(); 308} 309 310fixed4 frag (v2f i) : SV_Target 311{ 312 ToonerData tdata; 313 { 314 float3 full_vec_eye_to_geometry = i.worldPos - _WorldSpaceCameraPos; 315 float3 world_dir = normalize(i.worldPos - _WorldSpaceCameraPos); 316 float perspective_divide = 1.0 / i.pos.w; 317 float perspective_factor = length(full_vec_eye_to_geometry * perspective_divide); 318 tdata.screen_uv = i.screenPos.xy * perspective_divide; 319 tdata.screen_uv_round = floor(tdata.screen_uv * _ScreenParams.xy); 320 } 321 322 i.normal = -normalize(i.normal); 323 324#if defined(_OUTLINES) 325#if defined(_BASECOLOR_MAP) 326 float4 albedo = _MainTex.SampleBias(linear_repeat_s, i.uv0, _Global_Sample_Bias); 327 albedo *= _Color; 328#else 329 float4 albedo = _Color; 330#endif // _BASECOLOR_MAP 331 332#if defined(_RENDERING_CUTOUT) 333 clip(albedo.a - _Alpha_Cutoff); 334#endif 335 336 albedo = _Outline_Color; 337 338#if defined(_GIMMICK_AL_CHROMA_00) 339 float al_chroma_00_emission = 0; 340 if (_Gimmick_AL_Chroma_00_Outline_Pass && AudioLinkIsAvailable()) { 341 float3 c = AudioLinkData(ALPASS_CCSTRIP + uint2(0, 0)).rgb; 342#if defined(_GIMMICK_AL_CHROMA_00_HUE_SHIFT) 343 c = LRGBtoOKLCH(c); 344 c[2] += _Gimmick_AL_Chroma_00_Hue_Shift_Theta * 2.0 * 3.14159265; 345 c = OKLCHtoLRGB(c); 346#endif 347 albedo.rgb = lerp(albedo.rgb, c, _Gimmick_AL_Chroma_00_Outline_Blend); 348 al_chroma_00_emission = _Gimmick_AL_Chroma_00_Outline_Emission; 349 } 350#endif 351 352#if defined(_OKLAB) 353 // Do hue shift in perceptually uniform color space so it doesn't look like 354 // shit. 355 float oklab_mask = _OKLAB_Mask.SampleBias(linear_repeat_s, i.uv0, _Global_Sample_Bias); 356 if (oklab_mask > 0.01 && 357 (_OKLAB_Hue_Shift > 1E-6 || 358 abs(_OKLAB_Chroma_Shift) > 1E-6 || 359 abs(_OKLAB_Lightness_Shift) > 1E-6)) { 360 float3 c = albedo.rgb; 361 c = LRGBtoOKLCH(c); 362 c.x += _OKLAB_Lightness_Shift; 363 c.y += _OKLAB_Chroma_Shift; 364 c.z += _OKLAB_Hue_Shift; 365 c = OKLCHtoLRGB(c); 366 albedo.rgb = c; 367 } 368#endif 369 370 float4 vertex_light_color = 0; 371 float ao = 1; 372 float4 result = getLitColor( 373 vertex_light_color, 374 albedo, i.worldPos, i.normal, 375 /*metallic=*/0, /*smoothness=*/0, 376 i.uv0, ao, /*enable_direct=*/false, 377 /*diffuse_contrib=*/0, i, tdata); 378 379 result += albedo * _Outline_Emission_Strength; 380#if defined(_GIMMICK_AL_CHROMA_00) 381 result += albedo * al_chroma_00_emission; 382#endif 383 384#if defined(_EXPLODE) && defined(_AUDIOLINK) 385 if (AudioLinkIsAvailable() && _Explode_Phase > 1E-6) { 386 float4 al_color = 387 AudioLinkData( 388 ALPASS_CCLIGHTS + 389 uint2(uint(i.uv0.x * 8) + uint(i.uv0.y * 16) * 8, 0 )).rgba; 390 result = lerp(result, al_color, _Explode_Phase * _Explode_Phase); 391 } 392#endif 393 394 return result; 395#else 396 discard; 397 return 0; 398#endif // _OUTLINES 399} 400 401#endif // __TOONER_OUTLINE_PASS 402