yum-archive/Tooner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum-archive/Tooner

yumBegin adding gimmicks for downstairs map4880e41

master
16.6 KiB514 linesraw
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*/