summaryrefslogtreecommitdiffstats
path: root/Third_Party/at.pimaker.ltcgi
diff options
context:
space:
mode:
Diffstat (limited to 'Third_Party/at.pimaker.ltcgi')
-rw-r--r--Third_Party/at.pimaker.ltcgi/LICENSE69
-rw-r--r--Third_Party/at.pimaker.ltcgi/README.md72
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI.cginc434
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_AudioLinkNoOp.cginc1
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_config.cginc87
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_functions.cginc514
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_shadowmap.cginc93
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_structs.cginc43
-rw-r--r--Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_uniform.cginc146
9 files changed, 1459 insertions, 0 deletions
diff --git a/Third_Party/at.pimaker.ltcgi/LICENSE b/Third_Party/at.pimaker.ltcgi/LICENSE
new file mode 100644
index 0000000..6249e15
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/LICENSE
@@ -0,0 +1,69 @@
+MIT License
+
+Copyright (c) 2022 _pi_/pimaker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+Regarding the files 'Shaders/LTCGI.cginc' and 'Shaders/LTCGI_functions.cginc':
+Parts of the code in these file are adapted from the example code found here:
+
+ https://github.com/selfshadow/ltc_code
+
+Modifications by _pi_ (@pimaker on GitHub), licensed under the terms of the
+MIT license as far as applicable.
+
+Original copyright notice:
+
+Copyright (c) 2017, Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* If you use (or adapt) the source code in your own work, please include a
+ reference to the paper:
+ Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
+ Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+ ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
+ Project page: https://eheitzresearch.wordpress.com/415-2/
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+The following files are licensed explicitly, and may not be modified or used
+in commercial projects, but can be redistributed and displayed otherwise,
+provided this license is kept:
+
+ Propaganda/pi_graffiti.png
+ Propaganda/ltcgi_graffiti.png
diff --git a/Third_Party/at.pimaker.ltcgi/README.md b/Third_Party/at.pimaker.ltcgi/README.md
new file mode 100644
index 0000000..3da22c8
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/README.md
@@ -0,0 +1,72 @@
+# [LTCGI](https://ltcgi.dev)
+
+LTCGI is an optimized plug-and-play realtime area light solution using the [linearly transformed cosine algorithm](#LTC) for standalone Unity and VRChat. Free to use with [attribution](#Attribution). It can utilize the Unity build-in lightmapper or [Bakery](https://assetstore.unity.com/packages/tools/level-design/bakery-gpu-lightmapper-122218) for realistic shadows on static objects.
+
+![screenshot of standalone demo app](~Screenshots/demoapp.jpg)
+
+# Check out the [**official website**](https://ltcgi.dev) for documentation and a "Getting Started" guide! (https://ltcgi.dev)
+
+Consider the [attribution requirements](#Attribution). Check the [Releases](https://github.com/pimaker/ltcgi/releases) tab for downloads.
+
+You can also [download](https://github.com/PiMaker/ltcgi/raw/main/~DemoApp.zip) the standalone demo app pictured above to try it out for yourself.
+Alternatively, join the [demo world](https://vrchat.com/home/launch?worldId=wrld_aa2627ec-c63a-4db2-aa3e-9078d41c6d9c) in VRChat.
+
+[Read the FAQ](https://ltcgi.dev/FAQ) before asking for help anywhere! Once you've done that, feel free to join my Discord and ask for help: https://discord.gg/r38vJd2DuJ
+
+## Download
+
+For VRChat, you can install LTCGI via the [Creator Companion](https://vcc.docs.vrchat.com/) from my VPM repository:
+
+### ⬇️ **[Creator Companion/VPM Install](https://vpm.pimaker.at/)**
+
+For **standalone Unity**, you can import LTCGI as a [git package](https://docs.unity3d.com/2019.4/Documentation/Manual/upm-ui-giturl.html) with the URL: `https://github.com/PiMaker/ltcgi.git`
+
+**Adapters** for various VRChat video players are still distributed as unitypackages from the [Releases tab](https://github.com/pimaker/ltcgi/releases).
+
+## Supported shaders
+
+To use LTCGI, all objects that should receive lighting must use a compatible shader. Currently compatible ones are listed below. If you implement LTCGI into your shader, feel free to send a PR to be included.
+
+* [ORL Shader Family](https://shaders.orels.sh/) by [@orels1](https://github.com/orels1)
+* [Silent's Filamented](https://gitlab.com/s-ilent/filamented)
+* [Mochie's Unity Shaders](https://github.com/MochiesCode/Mochies-Unity-Shaders)
+* [Hekky Shaders](https://github.com/hyblocker/hekky-shaders)
+* [z3y's Shaders](https://github.com/z3y/shaders)
+* Basic "Unlit" Test Shader (included)
+* Surface Shader (included)
+
+## Attribution
+
+According to the [License](#License) you are free to use this in your world, but you need to give credit. You are free to do so in whichever way, but you must provide a link to this GitHub repository, such as to fulfill the imported license of the LTC example code used as a base for this project.
+
+For your convenience, a prefab called `LTCGI Attribution` is provided in the package.
+
+![LTCGI Attribution Prefab](~Screenshots/attribution.jpg)
+
+If you don't want to use it, instead display text similar to the following:
+
+```
+This project/world uses LTCGI by _pi_, see 'github.com/pimaker/ltcgi'.
+```
+
+# Licensing
+
+## The LTC algorithm
+
+Based on this paper:
+```
+Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
+Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
+Project page: https://eheitzresearch.wordpress.com/415-2/
+```
+[Read more](https://eheitzresearch.wordpress.com/415-2/)
+
+## LTCGI
+
+This project is made available under the terms of the MIT license, unless explicitly marked otherwise in the source files. See `LICENSE` for more.
+
+The following files are licensed explicitly, and may not be modified or used in commercial projects, but can be redistributed and displayed otherwise, provided this license is kept:
+
+* Propaganda/pi_graffiti.png
+* Propaganda/ltcgi_graffiti.png
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI.cginc
new file mode 100644
index 0000000..b976972
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI.cginc
@@ -0,0 +1,434 @@
+#ifndef LTCGI_INCLUDED
+#define LTCGI_INCLUDED
+
+#include "LTCGI_config.cginc"
+
+#ifdef LTCGI_AVATAR_MODE
+ #undef LTCGI_STATIC_UNIFORMS
+ #undef LTCGI_BICUBIC_LIGHTMAP
+ #define LTCGI_ALWAYS_LTC_DIFFUSE
+ // for perf and locality don't allow cylinders on avatars for now (it probably would be misdetected anyway)
+ #undef LTCGI_CYLINDER
+#endif
+
+#ifdef LTCGI_TOGGLEABLE_SPEC_DIFF_OFF
+ #undef LTCGI_DIFFUSE_OFF
+ #undef LTCGI_SPECULAR_OFF
+#endif
+
+#if defined(LTCGI_V2_CUSTOM_INPUT) || defined(LTCGI_V2_DIFFUSE_CALLBACK) || defined(LTCGI_V2_SPECULAR_CALLBACK)
+ #define LTCGI_API_V2
+#endif
+
+#include "LTCGI_structs.cginc"
+#include "LTCGI_uniform.cginc"
+#include "LTCGI_functions.cginc"
+#include "LTCGI_shadowmap.cginc"
+
+#ifdef SHADER_TARGET_SURFACE_ANALYSIS
+#define const
+#endif
+
+// Main function - this calculates the approximated model for one pixel and one light
+void LTCGI_Evaluate(ltcgi_input input, float3 worldNorm, float3 viewDir, float3x3 Minv, float roughness, const bool diffuse, out ltcgi_output output) {
+ output.input = input;
+ output.color = input.rawColor; // copy for colormode static
+ output.intensity = 0;
+
+ // diffuse distance fade
+ #ifdef LTCGI_DISTANCE_FADE_APPROX
+ if (diffuse) // static branch, specular does not directly fade with distance
+ {
+ if (!input.flags.lmdOnly) {
+ // very approximate lol
+ float3 ctr = (input.Lw[0] + input.Lw[1]) * 0.5f;
+ if (dot(ctr, ctr) > LTCGI_DISTANCE_FADE_APPROX_MULT * LTCGI_DISTANCE_FADE_APPROX_MULT)
+ {
+ return;
+ }
+ }
+ }
+ #endif
+
+ #define RET1_IF_LMDIFF [branch] if (/*const*/ diffuse && input.flags.diffFromLm) { output.intensity = 1.0f; return; }
+
+ [branch]
+ if (input.flags.colormode == LTCGI_COLORMODE_SINGLEUV) {
+ float2 uv = input.uvStart;
+ if (uv.x < 0) uv.xy = uv.yx;
+ // TODO: make more configurable?
+ #ifdef LTCGI_VISUALIZE_SAMPLE_UV
+ output.color = float3(uv.xy, 0);
+ #elif !defined(SHADER_TARGET_SURFACE_ANALYSIS)
+ // sample video texture directly for accuracy
+ float3 sampled = _Udon_LTCGI_Texture_LOD0.SampleLevel(LTCGI_SAMPLER, uv.xy, 0).rgb;
+ output.color *= sampled;
+ #endif
+
+ RET1_IF_LMDIFF
+ }
+
+ #ifdef LTCGI_AUDIOLINK
+ [branch]
+ if (input.flags.colormode == LTCGI_COLORMODE_AUDIOLINK) {
+ float al = AudioLinkData(ALPASS_AUDIOLINK + uint2(0, input.flags.alBand)).r;
+ output.color *= al;
+
+ RET1_IF_LMDIFF
+ }
+ #endif
+
+ // create LTC polygon array
+ // note the order of source verts (keyword: winding order)
+ float3 L[5];
+ L[0] = mul(Minv, input.Lw[0]);
+ L[1] = mul(Minv, input.Lw[1]);
+ L[2] = input.isTri ? L[1] : mul(Minv, input.Lw[3]);
+ L[3] = mul(Minv, input.Lw[2]);
+ L[4] = 0;
+
+ // get texture coords (before clipping!)
+ [branch]
+ if (input.flags.colormode == LTCGI_COLORMODE_TEXTURE) {
+ float3 RN;
+ float2 uv = LTCGI_calculateUV(input.i, input.flags, L, input.isTri, input.uvStart, input.uvEnd, RN);
+ float planeAreaSquared = dot(RN, RN);
+ float planeDistxPlaneArea = dot(RN, L[0]);
+
+ float3 sampled;
+ [branch]
+ if (diffuse) { // static branch
+ #ifdef LTCGI_BLENDED_DIFFUSE_SAMPLING
+ float3 sampled1;
+ LTCGI_sample(uv, 3, input.flags.texindex, 10, sampled1);
+ float3 sampled2;
+ LTCGI_sample(uv, 3, input.flags.texindex, 100, sampled2);
+ sampled =
+ sampled1 * 0.75 +
+ sampled2 * 0.25;
+ #else
+ LTCGI_sample(uv, 3, input.flags.texindex, 10, sampled);
+ #endif
+ } else {
+ float d = abs(planeDistxPlaneArea) / planeAreaSquared;
+ d *= LTCGI_UV_BLUR_DISTANCE;
+ d = log(d) / log(3.0);
+
+ // a rough material must never show a perfect reflection,
+ // since our LOD0 texture is not prefiltered (and thus cannot
+ // depict any blur correctly) - without this there is artifacting
+ // on the border of LOD0 and LOD1
+ d = clamp(d, saturate(roughness * 5.75), 1000);
+
+ LTCGI_trilinear(uv, d, input.flags.texindex, sampled);
+ }
+
+ // colorize output
+ output.color *= sampled;
+ }
+
+ RET1_IF_LMDIFF
+ #undef RET1_IF_LMDIFF
+
+ int n;
+ LTCGI_ClipQuadToHorizon(L, n);
+
+ // early out if everything was clipped below horizon
+ [branch]
+ if (n == 0)
+ return;
+
+ L[0] = normalize(L[0]);
+ L[1] = normalize(L[1]);
+ L[2] = normalize(L[2]);
+ L[3] = normalize(L[3]);
+
+ // integrate
+ float sum = 0;
+ sum += LTCGI_IntegrateEdge(L[0], L[1]).z;
+ sum += LTCGI_IntegrateEdge(L[1], L[2]).z;
+ sum += LTCGI_IntegrateEdge(L[2], L[3]).z;
+ [branch]
+ if (n >= 4)
+ {
+ L[4] = normalize(L[4]);
+ sum += LTCGI_IntegrateEdge(L[3], L[4]).z;
+ [branch]
+ if (n == 5)
+ sum += LTCGI_IntegrateEdge(L[4], L[0]).z;
+ }
+
+ // doublesided is accounted for with optimization at the start, so return abs
+ output.intensity = abs(sum);
+ return;
+}
+
+// Calculate light contribution for all lights,
+// call this from your shader and use the "diffuse" and "specular" outputs
+// lmuv is the raw lightmap UV coordinate (e.g. UV1)
+void LTCGI_Contribution(
+#ifdef LTCGI_API_V2
+ inout LTCGI_V2_CUSTOM_INPUT data,
+#endif
+ float3 worldPos, float3 worldNorm, float3 viewDir, float roughness, float2 lmuv
+#ifndef LTCGI_API_V2
+ , inout half3 diffuse, inout half3 specular, out float totalSpecularIntensity, out float totalDiffuseIntensity
+#endif
+) {
+ #ifndef LTCGI_API_V2
+ totalSpecularIntensity = 0;
+ totalDiffuseIntensity = 0;
+ #endif
+
+ #ifdef LTCGI_SPECULAR_OFF
+ specular = 0;
+ #endif
+ #ifdef LTCGI_DIFFUSE_OFF
+ diffuse = 0;
+ #endif
+
+ [branch]
+ if (_Udon_LTCGI_GlobalEnable == 0.0f) {
+ return;
+ }
+
+ // sample lookup tables
+ float theta = LTCGI_acos_fast(dot(worldNorm, viewDir));
+ float2 uv = float2(roughness, theta/(0.5*UNITY_PI));
+ uv = uv*LUT_SCALE + LUT_BIAS;
+
+ // calculate LTCGI custom lightmap UV and sample
+ float3 lms = LTCGI_SampleShadowmap(lmuv);
+
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+ // sample BDRF approximation from lookup texture
+ float4 t = _Udon_LTCGI_lut1.SampleLevel(LTCGI_SAMPLER, uv, 0);
+ #endif
+ float3x3 Minv = float3x3(
+ float3( 1, 0, t.w),
+ float3( 0, t.z, 0),
+ float3(t.y, 0, t.x)
+ );
+
+ // construct orthonormal basis around N
+ float3 T1, T2;
+ T1 = normalize(viewDir - worldNorm*dot(viewDir, worldNorm));
+ T2 = cross(worldNorm, T1);
+
+ // for diffuse lighting we assume the identity matrix as BDRF, so the
+ // LTC approximation is directly equivalent to the orthonormal rotation matrix
+ float3x3 identityBrdf = float3x3(float3(T1), float3(T2), float3(worldNorm));
+ // rotate area light in (T1, T2, N) basis for actual BRDF matrix as well
+ Minv = mul(Minv, identityBrdf);
+
+ // specular brightness
+ float spec_amp = 1.0f;
+ #ifndef LTCGI_SPECULAR_OFF
+ #ifndef LTCGI_DISABLE_LUT2
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+ spec_amp = _Udon_LTCGI_lut2.SampleLevel(LTCGI_SAMPLER, uv, 0).x;
+ #endif
+ #endif
+ #endif
+
+ bool noLm = false;
+ #ifdef LTCGI_LTC_DIFFUSE_FALLBACK
+ #ifndef LTCGI_ALWAYS_LTC_DIFFUSE
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS
+ float2 lmSize;
+ _Udon_LTCGI_Lightmap.GetDimensions(lmSize.x, lmSize.y);
+ noLm = lmSize.x == 1;
+ #endif
+ #endif
+ #endif
+ #ifdef LTCGI_ALWAYS_LTC_DIFFUSE
+ noLm = true;
+ #endif
+
+ // loop through all lights and add them to the output
+#if MAX_SOURCES != 1
+ uint count = min(_Udon_LTCGI_ScreenCount, MAX_SOURCES);
+ [loop]
+#else
+ // mobile config
+ const uint count = 1;
+ [unroll(1)]
+#endif
+ for (uint i = 0; i < count; i++) {
+ // skip masked and black lights
+ if (_Udon_LTCGI_Mask[i]) continue;
+ float4 extra = _Udon_LTCGI_ExtraData[i];
+ float3 color = extra.rgb;
+ if (!any(color)) continue;
+
+ ltcgi_flags flags = ltcgi_parse_flags(asuint(extra.w), noLm);
+
+ #ifdef LTCGI_ALWAYS_LTC_DIFFUSE
+ // can't honor a lightmap-only light in this mode
+ if (flags.lmdOnly) continue;
+ #endif
+
+ #ifdef LTCGI_TOGGLEABLE_SPEC_DIFF_OFF
+ // compile branches below away statically
+ flags.diffuse = flags.specular = true;
+ #endif
+
+ // calculate (shifted) world space positions
+ float3 Lw[4];
+ float4 uvStart = (float4)0, uvEnd = (float4)0;
+ bool isTri = false;
+ if (flags.lmdOnly) {
+ Lw[0] = Lw[1] = Lw[2] = Lw[3] = (float3)0;
+ } else {
+ LTCGI_GetLw(i, flags, worldPos, Lw, uvStart, uvEnd, isTri);
+ }
+
+ // skip single-sided lights that face the other way
+ float3 screenNorm = cross(Lw[1] - Lw[0], Lw[2] - Lw[0]);
+ if (!flags.doublesided) {
+ if (dot(screenNorm, Lw[0]) < 0)
+ continue;
+ }
+
+ float lm = 1;
+ if (flags.lmch) {
+ lm = lms[flags.lmch - 1];
+ if (lm < 0.001) continue;
+ }
+
+ ltcgi_input input;
+ input.i = i;
+ input.Lw = Lw;
+ input.isTri = isTri;
+ input.uvStart = uvStart;
+ input.uvEnd = uvEnd;
+ input.rawColor = color;
+ input.flags = flags;
+ input.screenNormal = screenNorm;
+
+ // diffuse lighting
+ #ifndef LTCGI_DIFFUSE_OFF
+ [branch]
+ if (flags.diffuse)
+ {
+ float lmd = lm;
+ if (flags.lmch) {
+ if (flags.diffFromLm)
+ lmd *= _Udon_LTCGI_LightmapMult[flags.lmch - 1];
+ else
+ lmd = smoothstep(0.0, LTCGI_SPECULAR_LIGHTMAP_STEP, saturate(lm - LTCGI_LIGHTMAP_CUTOFF));
+ }
+ ltcgi_output diff;
+ diff.color = 0;
+ LTCGI_Evaluate(input, worldNorm, viewDir, identityBrdf, roughness, true, diff);
+ diff.intensity *= lmd;
+
+ #ifdef LTCGI_API_V2
+ LTCGI_V2_DIFFUSE_CALLBACK(data, diff);
+ #else
+ // simply accumulate all lights
+ diffuse += (diff.intensity * diff.color);
+ totalDiffuseIntensity += diff.intensity;
+ #endif
+ }
+ #endif
+
+ // specular lighting
+ #ifndef LTCGI_SPECULAR_OFF
+ [branch]
+ if (flags.specular)
+ {
+ ltcgi_output spec;
+ spec.color = 0;
+ LTCGI_Evaluate(input, worldNorm, viewDir, Minv, roughness, false, spec);
+ spec.intensity *= spec_amp * smoothstep(0.0, LTCGI_SPECULAR_LIGHTMAP_STEP, saturate(lm - LTCGI_LIGHTMAP_CUTOFF));
+
+ #ifdef LTCGI_API_V2
+ LTCGI_V2_SPECULAR_CALLBACK(data, spec);
+ #else
+ // simply accumulate all lights
+ specular += spec.intensity * spec.color;
+ totalSpecularIntensity += spec.intensity;
+ #endif
+ }
+ #endif
+ }
+}
+
+// COMPATIBILITY FALLBACKS
+
+#ifndef LTCGI_API_V2
+
+// missing totalSpecularIntensity, totalDiffuseIntensity, specular
+void LTCGI_Contribution(
+ float3 worldPos, float3 worldNorm, float3 viewDir, float roughness, float2 lmuv, inout half3 diffuse
+) {
+ half3 _u1 = (half3)0;
+ float _u2, _u3;
+ LTCGI_Contribution(worldPos, worldNorm, viewDir, roughness, lmuv, diffuse, _u1, _u2, _u3);
+}
+
+// missing totalSpecularIntensity, totalDiffuseIntensity
+void LTCGI_Contribution(
+ float3 worldPos, float3 worldNorm, float3 viewDir, float roughness, float2 lmuv, inout half3 diffuse, inout half3 specular
+) {
+ float _u1, _u2;
+ LTCGI_Contribution(worldPos, worldNorm, viewDir, roughness, lmuv, diffuse, specular, _u1, _u2);
+}
+
+// missing totalDiffuseIntensity
+void LTCGI_Contribution(
+ float3 worldPos, float3 worldNorm, float3 viewDir, float roughness, float2 lmuv, inout half3 diffuse, inout half3 specular, out float totalSpecularIntensity
+) {
+ float _u1;
+ LTCGI_Contribution(worldPos, worldNorm, viewDir, roughness, lmuv, diffuse, specular, totalSpecularIntensity, _u1);
+}
+
+#endif
+
+/*
+
+Parts of the code in this file are adapted from the example code found here:
+
+ https://github.com/selfshadow/ltc_code
+
+Modifications by _pi_ (@pimaker on GitHub), licensed under the terms of the
+MIT license as far as applicable.
+
+Original copyright notice:
+
+Copyright (c) 2017, Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* If you use (or adapt) the source code in your own work, please include a
+ reference to the paper:
+
+ Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
+ Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+ ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
+ Project page: https://eheitzresearch.wordpress.com/415-2/
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+#endif
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_AudioLinkNoOp.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_AudioLinkNoOp.cginc
new file mode 100644
index 0000000..3ae4ee9
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_AudioLinkNoOp.cginc
@@ -0,0 +1 @@
+// this space intentionally left blank
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_config.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_config.cginc
new file mode 100644
index 0000000..91f6751
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_config.cginc
@@ -0,0 +1,87 @@
+#ifndef LTCGI_CONFIG_INCLUDED
+#define LTCGI_CONFIG_INCLUDED
+
+// Feel free to enable or disable (//) the options here.
+// They will apply to all LTCGI materials in the project.
+// Most of these can be changed in the LTCGI_Controller editor as well.
+
+/// No specular at all.
+//#define LTCGI_SPECULAR_OFF
+/// No diffuse at all.
+//#define LTCGI_DIFFUSE_OFF
+/// Disable the ability to toggle specular/diffuse on or off per screen.
+#define LTCGI_TOGGLEABLE_SPEC_DIFF_OFF
+
+/// Only use LTC diffuse mode, never lightmapped diffuse.
+/// This disables lightmaps entirely.
+#define LTCGI_ALWAYS_LTC_DIFFUSE
+
+/// Double-sample screen texture for diffuse lighting to smooth resulting lighting
+/// a bit more with global screen color data. Slight performance cost.
+//#define LTCGI_BLENDED_DIFFUSE_SAMPLING
+
+/// Disable extra specular detail LUT, saves a sampler.
+#define LTCGI_DISABLE_LUT2
+
+/// Use bicubic filtering for LTCGI lightmap. Recommended on.
+#define LTCGI_BICUBIC_LIGHTMAP
+
+/// Lightmap values below this will be treated as black for specular/LTC diffuse.
+#define LTCGI_LIGHTMAP_CUTOFF 0.1
+/// Lightmap values above this (plus cutoff) will be treated as white.
+#define LTCGI_SPECULAR_LIGHTMAP_STEP 0.3
+
+/// Distance multiplier for calculating blur amount.
+/// Increase to make reflections blurrier faster as distance increases.
+#define LTCGI_UV_BLUR_DISTANCE 333
+
+/// Fall back to LTC diffuse (from LM diffuse) on objects that are not marked static.
+#define LTCGI_LTC_DIFFUSE_FALLBACK
+
+/// Approximation to ignore diffuse light for far away
+/// lights, increase MULT or disable if you notice artifacting
+#define LTCGI_DISTANCE_FADE_APPROX
+/// Distance at which diffuse from screens will be ignored.
+#define LTCGI_DISTANCE_FADE_APPROX_MULT 50
+
+
+// disabled editor from here on out
+///
+
+
+// automatically kept in sync with LTCGI_Controller.cs
+#define MAX_SOURCES 16
+
+// set according to the LUT specified on CONTROLLER
+#define LUT_SIZE 256
+static float LUT_SCALE = (LUT_SIZE - 1.0)/LUT_SIZE;
+const float LUT_BIAS = 0.5/LUT_SIZE;
+
+// will be set automatically if audiolink is available and in use
+//#define LTCGI_AUDIOLINK
+
+#ifdef LTCGI_AUDIOLINK
+#ifndef AUDIOLINK_WIDTH
+#ifndef AUDIOLINK_CGINC_INCLUDED
+#include "LTCGI_AudioLinkNoOp.cginc"
+#define AUDIOLINK_CGINC_INCLUDED
+#endif
+#endif
+#endif
+
+// Bake screen data into texture for better performance. Disables moveable screens.
+#define LTCGI_STATIC_UNIFORMS
+
+// Allow statically textured lights.
+#define LTCGI_STATIC_TEXTURES
+
+// Enable support for cylindrical screens.
+//#define LTCGI_CYLINDER
+
+// Activate avatar mode, which overrides certain configs from above.
+//#define LTCGI_AVATAR_MODE
+
+// Slightly simplified and thus faster sampling for reflections at the cost of quality.
+//#define LTCGI_FAST_SAMPLING
+
+#endif
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_functions.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_functions.cginc
new file mode 100644
index 0000000..08df1c7
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_functions.cginc
@@ -0,0 +1,514 @@
+#ifndef LTCGI_FUNCTIONS_INCLUDED
+#define LTCGI_FUNCTIONS_INCLUDED
+
+/*
+ LTC HELPERS
+*/
+
+float3 LTCGI_IntegrateEdge(float3 v1, float3 v2)
+{
+ float x = dot(v1, v2);
+ float y = abs(x);
+
+ float a = 0.8543985 + (0.4965155 + 0.0145206*y)*y;
+ float b = 3.4175940 + (4.1616724 + y)*y;
+ float v = a / b;
+ float theta_sintheta = (x > 0.0) ? v : 0.5*rsqrt(max(1.0 - x*x, 1e-7)) - v;
+
+ return cross(v1, v2) * theta_sintheta;
+}
+
+void LTCGI_ClipQuadToHorizon(inout float3 L[5], out int n)
+{
+ // detect clipping config
+ uint config = 0;
+ if (L[0].z > 0.0) config += 1;
+ if (L[1].z > 0.0) config += 2;
+ if (L[2].z > 0.0) config += 4;
+ if (L[3].z > 0.0) config += 8;
+
+ n = 0;
+
+ // This [forcecase] only works when the cases are ordered in a specific manner.
+ // It gives like 10%-20% performance boost, so *make sure to leave it on*!
+ // If it breaks however, see if [branch] fixes it, and if it does, start
+ // reordering cases at random until it works again.
+ // It seems the compiler somehow optimizes away anything but setting 'n' in
+ // some orderings, including the ascending and descending ones.
+ // I wish I was joking.
+ [forcecase]
+ switch (config) {
+ case 13: // V1 V3 V4 clip V2 <- tl;dr: this fecker has to be first or shader go boom
+ n = 5;
+ L[4] = L[3];
+ L[3] = L[2];
+ L[2] = -L[1].z * L[2] + L[2].z * L[1];
+ L[1] = -L[1].z * L[0] + L[0].z * L[1];
+ break;
+ case 15: // V1 V2 V3 V4 - most common
+ n = 4;
+ break;
+ case 9: // V1 V4 clip V2 V3
+ n = 4;
+ L[1] = -L[1].z * L[0] + L[0].z * L[1];
+ L[2] = -L[2].z * L[3] + L[3].z * L[2];
+ break;
+ case 0: // clip all
+ break;
+ case 1: // V1 clip V2 V3 V4
+ n = 3;
+ L[1] = -L[1].z * L[0] + L[0].z * L[1];
+ L[2] = -L[3].z * L[0] + L[0].z * L[3];
+ L[3] = L[0];
+ break;
+ case 2: // V2 clip V1 V3 V4
+ n = 3;
+ L[0] = -L[0].z * L[1] + L[1].z * L[0];
+ L[2] = -L[2].z * L[1] + L[1].z * L[2];
+ L[3] = L[0];
+ break;
+ case 3: // V1 V2 clip V3 V4
+ n = 4;
+ L[2] = -L[2].z * L[1] + L[1].z * L[2];
+ L[3] = -L[3].z * L[0] + L[0].z * L[3];
+ break;
+ case 4: // V3 clip V1 V2 V4
+ n = 3;
+ L[0] = -L[3].z * L[2] + L[2].z * L[3];
+ L[1] = -L[1].z * L[2] + L[2].z * L[1];
+ L[3] = L[0];
+ break;
+ case 5: // V1 V3 clip V2 V4) impossible
+ break;
+ case 6: // V2 V3 clip V1 V4
+ n = 4;
+ L[0] = -L[0].z * L[1] + L[1].z * L[0];
+ L[3] = -L[3].z * L[2] + L[2].z * L[3];
+ break;
+ case 7: // V1 V2 V3 clip V4
+ n = 5;
+ L[4] = -L[3].z * L[0] + L[0].z * L[3];
+ L[3] = -L[3].z * L[2] + L[2].z * L[3];
+ break;
+ case 8: // V4 clip V1 V2 V3
+ n = 3;
+ L[0] = -L[0].z * L[3] + L[3].z * L[0];
+ L[1] = -L[2].z * L[3] + L[3].z * L[2];
+ L[2] = L[3];
+ break;
+ case 10: // V2 V4 clip V1 V3) impossible
+ break;
+ case 11: // V1 V2 V4 clip V3
+ n = 5;
+ L[4] = L[3];
+ L[3] = -L[2].z * L[3] + L[3].z * L[2];
+ L[2] = -L[2].z * L[1] + L[1].z * L[2];
+ break;
+ case 12: // V3 V4 clip V1 V2
+ n = 4;
+ L[1] = -L[1].z * L[2] + L[2].z * L[1];
+ L[0] = -L[0].z * L[3] + L[3].z * L[0];
+ break;
+ case 14: // V2 V3 V4 clip V1
+ n = 5;
+ L[4] = -L[0].z * L[3] + L[3].z * L[0];
+ L[0] = -L[0].z * L[1] + L[1].z * L[0];
+ break;
+ }
+
+ // inlining these branches *unconditionally* breaks the [forcecase] above
+ // ...yeah I know
+ if (n == 3)
+ L[3] = L[0];
+ if (n == 4)
+ L[4] = L[0];
+}
+
+/*
+ TEXTURE SAMPLING
+*/
+
+float2 LTCGI_inset_uv(float2 uv)
+{
+ return uv * 0.75 + float2(0.125, 0.125);
+}
+
+half3 premul_alpha(half4 i)
+{
+ return i.rgb * i.a;
+}
+
+half max2(half2 v)
+{
+ return max(v.x, v.y);
+}
+
+void LTCGI_sample(float2 uv, uint lod, uint idx, float blend, out float3 result)
+{
+#ifndef LTCGI_STATIC_TEXTURES
+ idx = 0; // optimize away the branches below
+#endif
+
+#ifdef LTCGI_FAST_SAMPLING
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS
+ float outside = max2(abs(uv - 0.5f) - 0.5f);
+ float outmod = smoothstep(-0.1f, 0.1f, outside) * 2.5f;
+ blend = blend * 2.5f + outmod;
+ [branch]
+ if (idx == 0)
+ {
+ result = premul_alpha(_Udon_LTCGI_Texture_LOD0.SampleLevel(LTCGI_SAMPLER, uv, blend));
+ }
+ #ifdef LTCGI_STATIC_TEXTURES
+ else
+ {
+ result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD(
+ _Udon_LTCGI_Texture_LOD0_arr,
+ LTCGI_SAMPLER_RAW,
+ float3(uv, idx - 1),
+ blend
+ ).rgb;
+ }
+ #endif
+ #else
+ result = 0;
+ #endif
+#else
+ result = 0;
+
+ [branch]
+ if (lod == 0)
+ {
+ // if we're outside of the 0-1 UV space we must sample a prefiltered texture
+ [branch]
+ if(any(saturate(abs(uv - 0.5) - 0.5)))
+ {
+ lod = 1;
+ }
+ else
+ {
+ // LOD0 is the original texture itself, so not prefiltered, but we can
+ // approximate it a bit with trilinear lod
+ float lod = (1 - blend) * 1.5;
+ [branch]
+ if (idx == 0)
+ {
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS
+ result = premul_alpha(_Udon_LTCGI_Texture_LOD0.SampleLevel(LTCGI_SAMPLER, uv, lod));
+ return;
+ #else
+ result = 0;
+ return;
+ #endif
+ }
+ #ifdef LTCGI_STATIC_TEXTURES
+ else
+ {
+ result = premul_alpha(UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD(
+ _Udon_LTCGI_Texture_LOD0_arr,
+ LTCGI_SAMPLER_RAW,
+ float3(uv, idx - 1),
+ lod
+ ));
+ return;
+ }
+ #endif
+ }
+ }
+
+ float2 ruv = LTCGI_inset_uv(uv);
+
+ [branch]
+ if (idx == 0)
+ {
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS
+ switch (lod)
+ {
+ case 1:
+ result = _Udon_LTCGI_Texture_LOD1.SampleLevel(LTCGI_SAMPLER, ruv, 0).rgb;
+ return;
+ case 2:
+ result = _Udon_LTCGI_Texture_LOD2.SampleLevel(LTCGI_SAMPLER, ruv, 0).rgb;
+ return;
+ default:
+ result = _Udon_LTCGI_Texture_LOD3.SampleLevel(LTCGI_SAMPLER, ruv, blend*0.72).rgb;
+ return;
+ }
+ #else
+ result = 0;
+ return;
+ #endif
+ }
+ #ifdef LTCGI_STATIC_TEXTURES
+ else
+ {
+ [forcecase]
+ switch (lod)
+ {
+ case 1:
+ result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD(
+ _Udon_LTCGI_Texture_LOD1_arr,
+ LTCGI_SAMPLER_RAW,
+ float3(ruv, idx - 1),
+ 0
+ ).rgb;
+ return;
+ case 2:
+ result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD(
+ _Udon_LTCGI_Texture_LOD2_arr,
+ LTCGI_SAMPLER_RAW,
+ float3(ruv, idx - 1),
+ 0
+ ).rgb;
+ return;
+ default:
+ result = UNITY_SAMPLE_TEX2DARRAY_SAMPLER_LOD(
+ _Udon_LTCGI_Texture_LOD3_arr,
+ LTCGI_SAMPLER_RAW,
+ float3(ruv, idx - 1),
+ blend
+ ).rgb;
+ return;
+ }
+ }
+ #endif
+#endif
+}
+
+void LTCGI_trilinear(float2 uv, float d, uint idx, out float3 result)
+{
+#ifdef LTCGI_FAST_SAMPLING
+ LTCGI_sample(uv, 0, idx, d, result);
+#else
+ uint low = (uint)d;
+ uint high = low + 1;
+
+ // DEBUG: colorize d/lod
+ //return float3(low == 0, low == 1, low == 2);
+
+ if (low >= 3)
+ {
+ LTCGI_sample(uv, 3, idx, d - 3, result);
+ }
+ else
+ {
+ float amount = saturate(high - d);
+ float3 low_sample;
+ LTCGI_sample(uv, low, idx, amount, low_sample);
+ float3 high_sample;
+ LTCGI_sample(uv, high, idx, 0, high_sample);
+
+ result = lerp(high_sample, low_sample, amount);
+ }
+#endif
+}
+
+/*
+ GENERIC HELPERS
+*/
+
+// from: https://seblagarde.wordpress.com/2014/12/01/inverse-trigonometric-functions-gpu-optimization-for-amd-gcn-architecture/
+// max absolute error 9.0x10^-3
+// Eberly's polynomial degree 1 - respect bounds
+// 4 VGPR, 12 FR (8 FR, 1 QR), 1 scalar
+// input [-1, 1] and output [0, PI]
+float LTCGI_acos_fast(float inX)
+{
+ float x = abs(inX);
+ float res = -0.156583f * x + UNITY_HALF_PI;
+ res *= sqrt(1.0f - x);
+ return (inX >= 0) ? res : UNITY_PI - res;
+}
+
+bool LTCGI_tri_ray(float3 orig, float3 dir, float3 v0, float3 v1, float3 v2, out float2 bary) {
+ float3 v0v1 = v1 - v0;
+ float3 v0v2 = v2 - v0;
+ float3 pvec = cross(dir, v0v2);
+ float det = dot(v0v1, pvec);
+ float invDet = 1 / det;
+
+ float3 tvec = orig - v0;
+ bary.x = dot(tvec, pvec) * invDet;
+
+ float3 qvec = cross(tvec, v0v1);
+ bary.y = dot(dir, qvec) * invDet;
+
+ // return false when other triangle of quad should be sampled,
+ // i.e. we went over the diagonal line
+ return bary.x >= 0;
+}
+
+float2 LTCGI_rotateVector(float2 x, float angle)
+{
+ float c = cos(angle);
+ float s = sin(angle);
+ return mul(float2x2(c,s,-s,c), x);
+}
+
+/*float LTCGI_remap(float3 from, float3 to, float2 targetFrom, float2 targetTo, float3 value)
+{
+ float rel = (value - from) / (to - from);
+ return lerp(targetFrom, targetTo, rel);
+}*/
+
+float2 LTCGI_calculateUV(uint i, ltcgi_flags flags, float3 L[5], bool isTri, float4 uvStart, float4 uvEnd, out float3 ray)
+{
+ // calculate perpendicular vector to plane defined by area light
+ float3 E1 = L[1] - L[0];
+ float3 E2 = L[3] - L[0];
+ ray = cross(E1, E2);
+
+ // raycast it against the two triangles formed by the quad
+ float2 bary;
+ bool hit0 = LTCGI_tri_ray(0, ray, L[0], L[2], L[3], bary) || isTri;
+ if (!hit0) {
+ LTCGI_tri_ray(0, ray, L[0], L[1], L[2], bary);
+ }
+
+ float3 bary3 = float3(bary, 1 - bary.x - bary.y);
+ float2 uv;
+ if (hit0)
+ uv = uvEnd.zw * bary3.x + uvEnd.xy * bary3.y;
+ else
+ uv = uvStart.zw * bary3.x + uvEnd.zw * bary3.y;
+ return uv + uvStart.xy * bary3.z;
+}
+
+/*
+ EXPERIMENTAL: CYLINDER HELPER
+*/
+
+void LTCGI_GetLw(uint i, ltcgi_flags flags, float3 worldPos, out float3 Lw[4], out float4 uvStart, out float4 uvEnd, out bool isTri) {
+ bool cylinder = false;
+ #ifdef LTCGI_CYLINDER
+ // statically optimize out branch below in case disabled
+ cylinder = flags.cylinder;
+ #endif
+
+ float4 v0 = _Udon_LTCGI_Vertices_0_get(i);
+ float4 v1 = _Udon_LTCGI_Vertices_1_get(i);
+ float4 v2 = _Udon_LTCGI_Vertices_2_get(i);
+ float4 v3 = _Udon_LTCGI_Vertices_3_get(i);
+
+ [branch]
+ if (cylinder) {
+ // construct data according to worldPos to create aligned
+ // rectangle tangent to the cylinder
+
+ float3 in_base = v0.xyz;
+ float in_height = v0.w;
+ float in_radius = v1.w;
+ float in_size = v2.w;
+ float in_angle = v3.w;
+
+ // get angle between 2D unit plane and vector pointing from cylinder to shade point
+ float2 towardsCylinder = LTCGI_rotateVector((in_base - worldPos).xz, -in_angle);
+ float angle = atan2(towardsCylinder.x, towardsCylinder.y);
+ // clamp angle to size parameter, i.e. "width" of lit surface area
+ float angleClamped = clamp(angle, -in_size, in_size) + in_angle;
+ // construct vector that *most* faces shade point
+ float2 facing = float2(sin(angleClamped), cos(angleClamped));
+ // tangent of rectangular screen on cylinder surface used for calculating lighting for shade point
+ float2 tangent = float2(facing.y, -facing.x);
+ float2 onCylinderFacing = facing * in_radius;
+
+ // clip ends, approximately
+ float rclip = saturate(lerp(1, 0, (angleClamped - in_angle) - (in_size - UNITY_HALF_PI*0.5f)));
+ float lclip = saturate(lerp(1, 0, -(angleClamped - in_angle) - (in_size - UNITY_HALF_PI*0.5f)));
+
+ float2 p1 = in_base.xz - onCylinderFacing + tangent * in_radius * lclip;
+ float2 p2 = in_base.xz - onCylinderFacing - tangent * in_radius * rclip;
+
+ Lw[0] = float3(p1.x, in_base.y, p1.y) - worldPos;
+ Lw[1] = float3(p1.x, in_base.y + in_height, p1.y) - worldPos;
+ Lw[2] = float3(p2.x, in_base.y, p2.y) - worldPos;
+ Lw[3] = float3(p2.x, in_base.y + in_height, p2.y) - worldPos;
+
+ isTri = false;
+
+ // UV depends on "viewing" angle of the shade point towards the cylinder
+ float2 viewDir = normalize((in_base - worldPos).xz);
+ // forwardAngle == atan2(cos(in_angle), sin(in_angle)); but only negative
+ float forwardAngle = -in_angle + UNITY_HALF_PI;
+ // offset from center of screen forward to the side ends, positive goes left/ccw fpv top,
+ // sine to account for the fact we're rotating around a cylinder which has depth
+ float viewAngle = forwardAngle - atan2(viewDir.y, viewDir.x);
+ // prevent rollover, since we need to clamp we must stay withing [-pi, pi]
+ if (viewAngle < -UNITY_PI)
+ viewAngle += UNITY_TWO_PI;
+ if (viewAngle > UNITY_PI)
+ viewAngle -= UNITY_TWO_PI;
+ viewAngle = clamp(viewAngle * 0.5f, -in_size, in_size);
+ viewAngle = sin(viewAngle);
+ // full view UVs, but shifted left/right depending on view angle
+ float2 uvStart2 = float2(1 - saturate(viewAngle), 0);
+ float2 uvEnd2 = float2(1 - saturate(viewAngle + 1), 1);
+ uvStart = float4(uvStart2.x, uvStart2.y, uvStart2.x, uvEnd2.y);
+ uvEnd = float4(uvEnd2.x, uvStart2.y, uvEnd2.x, uvEnd2.y);
+
+ } else {
+ // use passed in data, offset around worldPos
+ Lw[0] = v0.xyz - worldPos;
+ Lw[1] = v1.xyz - worldPos;
+ Lw[2] = v2.xyz - worldPos;
+ Lw[3] = v3.xyz - worldPos;
+ #ifndef SHADER_TARGET_SURFACE_ANALYSIS
+ uvStart = _Udon_LTCGI_static_uniforms[uint2(4, i)];
+ uvEnd = _Udon_LTCGI_static_uniforms[uint2(5, i)];
+ #else
+ uvStart = float4(0, 0, 1, 0);
+ uvEnd = float4(1, 1, 0, 1);
+ #endif
+
+ // we only detect triangles for "blender" import configuration, as those are the only
+ // ones that can actually be triangles (I think?)
+ isTri = /*distance(Lw[2], Lw[3]) < 0.001 || */distance(Lw[1], Lw[3]) < 0.001;
+ }
+}
+
+#endif
+
+/*
+
+Parts of the code in this file are adapted from the example code found here:
+
+ https://github.com/selfshadow/ltc_code
+
+Modifications by _pi_ (@pimaker on GitHub), licensed under the terms of the
+MIT license as far as applicable.
+
+Original copyright notice:
+
+Copyright (c) 2017, Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* If you use (or adapt) the source code in your own work, please include a
+ reference to the paper:
+
+ Real-Time Polygonal-Light Shading with Linearly Transformed Cosines.
+ Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt.
+ ACM Transactions on Graphics (Proceedings of ACM SIGGRAPH 2016) 35(4), 2016.
+ Project page: https://eheitzresearch.wordpress.com/415-2/
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/ \ No newline at end of file
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_shadowmap.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_shadowmap.cginc
new file mode 100644
index 0000000..2c4a598
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_shadowmap.cginc
@@ -0,0 +1,93 @@
+#ifndef LTCGI_SHADOWMAP_INCLUDED
+#define LTCGI_SHADOWMAP_INCLUDED
+
+// Adapted from: https://gitlab.com/s-ilent/filamented
+// Licensed under the terms of the Apache License 2.0
+// Full text: https://gitlab.com/s-ilent/filamented/-/blob/master/LICENSE
+//
+// Conforming to the terms of the above license, this file is redistributed
+// under the terms of the MIT license as part of the LTCGI shader package,
+// provided this notice is kept.
+
+#ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+
+float4 LTCGI_cubic(float v)
+{
+ float4 n = float4(1.0, 2.0, 3.0, 4.0) - v;
+ float4 s = n * n * n;
+ float x = s.x;
+ float y = s.y - 4.0 * s.x;
+ float z = s.z - 4.0 * s.y + 6.0 * s.x;
+ float w = 6.0 - x - y - z;
+ return float4(x, y, z, w);
+}
+
+// Unity's SampleTexture2DBicubic doesn't exist in 2018, which is our target here.
+// So this is a similar function with tweaks to have similar semantics.
+
+float4 LTCGI_SampleTexture2DBicubicFilter(Texture2D tex, SamplerState smp, float2 coord, float4 texSize, bool lightmap = false)
+{
+ coord = coord * texSize.xy - 0.5;
+ float fx = frac(coord.x);
+ float fy = frac(coord.y);
+ coord.x -= fx;
+ coord.y -= fy;
+
+ float4 xcubic = LTCGI_cubic(fx);
+ float4 ycubic = LTCGI_cubic(fy);
+
+ float4 c = float4(coord.x - 0.5, coord.x + 1.5, coord.y - 0.5, coord.y + 1.5);
+ float4 s = float4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x + ycubic.y, ycubic.z + ycubic.w);
+ float4 offset = c + float4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) / s;
+
+ float4 sample0 = tex.Sample(smp, float2(offset.x, offset.z) * texSize.zw);
+ float4 sample1 = tex.Sample(smp, float2(offset.y, offset.z) * texSize.zw);
+ float4 sample2 = tex.Sample(smp, float2(offset.x, offset.w) * texSize.zw);
+ float4 sample3 = tex.Sample(smp, float2(offset.y, offset.w) * texSize.zw);
+
+ if (lightmap) {
+ sample0 = float4(DecodeLightmap(sample0), 1.0);
+ sample1 = float4(DecodeLightmap(sample1), 1.0);
+ sample2 = float4(DecodeLightmap(sample2), 1.0);
+ sample3 = float4(DecodeLightmap(sample3), 1.0);
+ }
+
+ float sx = s.x / (s.x + s.y);
+ float sy = s.z / (s.z + s.w);
+
+ return lerp(
+ lerp(sample3, sample2, sx),
+ lerp(sample1, sample0, sx), sy);
+}
+
+float4 LTCGI_SampleShadowmap(float2 lmuv)
+{
+ #ifdef LTCGI_ALWAYS_LTC_DIFFUSE
+ return 1;
+ #else
+ lmuv = lmuv * _Udon_LTCGI_LightmapST.xy + _Udon_LTCGI_LightmapST.zw;
+
+ #ifdef LTCGI_BICUBIC_LIGHTMAP
+ float width, height;
+ _Udon_LTCGI_Lightmap.GetDimensions(width, height);
+
+ float4 _Udon_LTCGI_Lightmap_TexelSize = float4(width, height, 1.0/width, 1.0/height);
+
+ return LTCGI_SampleTexture2DBicubicFilter(
+ _Udon_LTCGI_Lightmap, LTCGI_SAMPLER,
+ lmuv, _Udon_LTCGI_Lightmap_TexelSize,
+ true
+ );
+ #else
+ fixed4 sample = _Udon_LTCGI_Lightmap.Sample(LTCGI_SAMPLER, lmuv);
+ return float4(DecodeLightmap(sample), 1.0);
+ #endif
+ #endif
+}
+
+#else
+// surface shader analysis stub
+float4 LTCGI_SampleShadowmap(float2 lmuv) { return 1; }
+#endif
+
+#endif \ No newline at end of file
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_structs.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_structs.cginc
new file mode 100644
index 0000000..164887f
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_structs.cginc
@@ -0,0 +1,43 @@
+#ifndef LTCGI_STRUCTS_INCLUDED
+#define LTCGI_STRUCTS_INCLUDED
+
+#define LTCGI_COLORMODE_STATIC 0
+#define LTCGI_COLORMODE_TEXTURE 1
+#define LTCGI_COLORMODE_SINGLEUV 2
+#define LTCGI_COLORMODE_AUDIOLINK 3
+
+struct ltcgi_flags
+{
+ bool doublesided; // if the light is doublesided or only illuminates the front face
+ bool diffFromLm; // diffuse lighting intensity will not be calculated via LTC but taken directly from the lightmap
+ bool specular; // if the light has a specular component
+ bool diffuse; // if the light has a diffuse component
+ uint colormode; // colormode, see above
+ uint texindex; // index of the texture to shade with, if colormode == LTCGI_COLORMODE_TEXTURE
+ uint lmch, lmidx; // lightmap channel and index
+ bool cylinder; // is this light a cylinder
+ uint alBand; // audiolink band if colormode == LTCGI_COLORMODE_AUDIOLINK
+ bool lmdOnly; // if this light is lightmap-diffuse _only_, that is, no LTC will be run (Lw will be all 0 in that case) - this will never be true on avatars (with LTCGI_ALWAYS_LTC_DIFFUSE)
+};
+
+struct ltcgi_input
+{
+ uint i; // light number
+ float3 Lw[4]; // world space area light vertices, Lw[1] == Lw[3] for triangle lights, shifted by input worldPos (i.e. world space position as seen from (0, 0, 0))
+ bool isTri; // if this is a triangle light, quad if false
+ float4 uvStart; // defines the UV layout of the area (xy = top-left, zw=top-right)
+ float4 uvEnd; // defines the UV layout of the area (xy = bottom-left, zw=bottom-right), different use for cylinders
+ float3 rawColor; // the raw light color, unaffected by any colormode calculations (i.e. exactly what's given as "color" in editor)
+ float3 screenNormal; // world space normal direction of area light
+ ltcgi_flags flags; // flags, see above
+};
+
+struct ltcgi_output
+{
+ ltcgi_input input; // input data that resulted in this output
+
+ float intensity; // intensity output by LTC calculation
+ float3 color; // color output by LTCGI colormode calculation
+};
+
+#endif \ No newline at end of file
diff --git a/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_uniform.cginc b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_uniform.cginc
new file mode 100644
index 0000000..2bf4673
--- /dev/null
+++ b/Third_Party/at.pimaker.ltcgi/Shaders/LTCGI_uniform.cginc
@@ -0,0 +1,146 @@
+#ifndef LTCGI_UNIFORM_INCLUDED
+#define LTCGI_UNIFORM_INCLUDED
+
+// global sampler (trilinear)
+#ifndef LTCGI_SAMPLER
+SamplerState sampler_LTCGI_trilinear_clamp_sampler;
+#define LTCGI_SAMPLER sampler_LTCGI_trilinear_clamp_sampler
+#define LTCGI_SAMPLER_RAW _LTCGI_trilinear_clamp_sampler
+#endif
+
+// LUTs
+#ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+#ifndef LTCGI_DISABLE_LUT2
+uniform Texture2D<float4> _Udon_LTCGI_lut2;
+#endif
+uniform Texture2D<float4> _Udon_LTCGI_lut1;
+#endif
+
+#ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+uniform Texture2D<float4> _Udon_LTCGI_static_uniforms;
+#endif
+
+#ifdef LTCGI_STATIC_UNIFORMS
+
+float4 _Udon_LTCGI_Vertices_0_get(uint i) {
+ return _Udon_LTCGI_static_uniforms[uint2(0, i)];
+}
+float4 _Udon_LTCGI_Vertices_1_get(uint i) {
+ return _Udon_LTCGI_static_uniforms[uint2(1, i)];
+}
+float4 _Udon_LTCGI_Vertices_2_get(uint i) {
+ return _Udon_LTCGI_static_uniforms[uint2(2, i)];
+}
+float4 _Udon_LTCGI_Vertices_3_get(uint i) {
+ return _Udon_LTCGI_static_uniforms[uint2(3, i)];
+}
+
+#else
+
+// vertices in object space; w component is UV (legacy)
+uniform float4 _Udon_LTCGI_Vertices_0[MAX_SOURCES];
+uniform float4 _Udon_LTCGI_Vertices_1[MAX_SOURCES];
+uniform float4 _Udon_LTCGI_Vertices_2[MAX_SOURCES];
+uniform float4 _Udon_LTCGI_Vertices_3[MAX_SOURCES];
+
+float4 _Udon_LTCGI_Vertices_0_get(uint i) {
+ return _Udon_LTCGI_Vertices_0[i];
+}
+float4 _Udon_LTCGI_Vertices_1_get(uint i) {
+ return _Udon_LTCGI_Vertices_1[i];
+}
+float4 _Udon_LTCGI_Vertices_2_get(uint i) {
+ return _Udon_LTCGI_Vertices_2[i];
+}
+float4 _Udon_LTCGI_Vertices_3_get(uint i) {
+ return _Udon_LTCGI_Vertices_3[i];
+}
+
+#endif
+
+// light source count, maximum is MAX_SOURCES
+uniform uint _Udon_LTCGI_ScreenCount;
+
+// per-renderer mask to select sources,
+// for max perf update _Udon_LTCGI_ScreenCount too
+uniform bool _Udon_LTCGI_Mask[MAX_SOURCES];
+
+// extra data per light source, layout:
+// color.r color.g color.b flags*
+// * b0=double-sided, b1=diffuse-from-lightmap, b2=specular, b3=diffuse,
+// b4-b7=texture index (0=video, (n>0)=n-1)
+// b8-b9=color mode
+// b10-b11=lightmap channel (0=disabled, 1=r, 2=g, 3=b)
+// b12=cylinder
+// b13-14=audio link band
+// b15=lightmap diffuse only
+// (color black = fully disabled)
+uniform float4 _Udon_LTCGI_ExtraData[MAX_SOURCES];
+
+ltcgi_flags ltcgi_parse_flags(uint val, bool noLmDiff)
+{
+ ltcgi_flags ret = (ltcgi_flags)0;
+ ret.doublesided = (val & 1) == 1;
+
+ #ifdef LTCGI_ALWAYS_LTC_DIFFUSE
+ ret.diffFromLm = false;
+ #else
+ ret.diffFromLm = !noLmDiff && (val & 2) == 2;
+ #endif
+
+ ret.diffuse = (val & 8) == 8;
+
+ ret.specular = (val & 4) == 4;
+ ret.texindex = (val & 0xf0) >> 4;
+ ret.colormode = (val & 0x300) >> 8;
+
+ #ifdef LTCGI_ALWAYS_LTC_DIFFUSE
+ ret.lmch = 0;
+ #else
+ ret.lmch = (val & 0xC00) >> 10;
+ #endif
+
+ ret.cylinder = (val & (1 << 12)) == (1 << 12);
+
+ #ifdef LTCGI_AUDIOLINK
+ ret.alBand = (val & 0x6000) >> 13;
+ #endif
+
+ ret.lmdOnly = (val & (1 << 15)) == (1 << 15);
+
+ return ret;
+}
+
+// video input
+#ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+uniform Texture2D<float4> _Udon_LTCGI_Texture_LOD0;
+#ifndef LTCGI_FAST_SAMPLING
+uniform Texture2D<float4> _Udon_LTCGI_Texture_LOD1;
+uniform Texture2D<float4> _Udon_LTCGI_Texture_LOD2;
+uniform Texture2D<float4> _Udon_LTCGI_Texture_LOD3;
+#endif
+#endif
+
+// static textures
+#ifdef LTCGI_STATIC_TEXTURES
+UNITY_DECLARE_TEX2DARRAY_NOSAMPLER(_Udon_LTCGI_Texture_LOD0_arr);
+#ifndef LTCGI_FAST_SAMPLING
+UNITY_DECLARE_TEX2DARRAY_NOSAMPLER(_Udon_LTCGI_Texture_LOD1_arr);
+UNITY_DECLARE_TEX2DARRAY_NOSAMPLER(_Udon_LTCGI_Texture_LOD2_arr);
+UNITY_DECLARE_TEX2DARRAY_NOSAMPLER(_Udon_LTCGI_Texture_LOD3_arr);
+#endif
+#endif
+
+// lightmap
+#ifndef SHADER_TARGET_SURFACE_ANALYSIS_MOJOSHADER
+#ifndef LTCGI_ALWAYS_LTC_DIFFUSE
+uniform Texture2D<float4> _Udon_LTCGI_Lightmap;
+#endif
+#endif
+uniform float3 _Udon_LTCGI_LightmapMult;
+uniform float4 _Udon_LTCGI_LightmapST;
+
+// global toggle
+uniform float _Udon_LTCGI_GlobalEnable;
+
+#endif \ No newline at end of file