@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: e65753410138a4801a6caf79d625ccd1 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 345755c04661c49208b308ceef47c557 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,13 @@ | |||||
{ | |||||
"name": "LeTai.TrueShadow", | |||||
"references": [], | |||||
"includePlatforms": [], | |||||
"excludePlatforms": [], | |||||
"allowUnsafeCode": false, | |||||
"overrideReferences": false, | |||||
"precompiledReferences": [], | |||||
"autoReferenced": true, | |||||
"defineConstraints": [], | |||||
"versionDefines": [], | |||||
"noEngineReferences": false | |||||
} |
@ -0,0 +1,7 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 7ab3663edede26740845931880bf22af | |||||
AssemblyDefinitionImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,7 @@ | |||||
fileFormatVersion: 2 | |||||
guid: c4e2187761ed8294780562fc58e8205e | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1 @@ | |||||
https://leloctai.com/trueshadow/docs/ |
@ -0,0 +1,7 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 63a70f84fa489504abf9f477bd12d05e | |||||
TextScriptImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: d5929e1d292a9d84180571372bbb84f6 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 316e6d240db2e974d97ea072bee96163 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,103 @@ | |||||
Shader "Hidden/EfficientBlur" | |||||
{ | |||||
Properties | |||||
{ | |||||
_MainTex ("Texture", 2D) = "white" {} | |||||
} | |||||
CGINCLUDE | |||||
#include "UnityCG.cginc" | |||||
sampler2D _MainTex; | |||||
half4 _MainTex_TexelSize; | |||||
half4 _MainTex_ST; | |||||
uniform half _Radius; | |||||
struct v2f | |||||
{ | |||||
half4 vertex : SV_POSITION; | |||||
half4 texcoord : TEXCOORD0; | |||||
}; | |||||
/*struct appdata | |||||
{ | |||||
half4 vertex : POSITION; | |||||
half2 texcoord: TEXCOORD0; | |||||
}*/ | |||||
v2f vert(appdata_img v) | |||||
{ | |||||
v2f o; | |||||
o.vertex = UnityObjectToClipPos(v.vertex); | |||||
half4 offset = half2(-0.5h, 0.5h).xxyy; //-x, -y, x, y | |||||
offset *= _MainTex_TexelSize.xyxy; | |||||
offset *= _Radius; | |||||
o.texcoord = v.texcoord.xyxy + offset; | |||||
return o; | |||||
} | |||||
half4 frag(v2f i) : SV_Target | |||||
{ | |||||
// half4 o = | |||||
// tex2D(_MainTex, i.texcoord.xw); | |||||
// o += tex2D(_MainTex, i.texcoord.zw); | |||||
// o += tex2D(_MainTex, i.texcoord.xy); | |||||
// o += tex2D(_MainTex, i.texcoord.zy); | |||||
// o /= 4.0; | |||||
//Pray to the compiler god these will MAD | |||||
half4 o = | |||||
tex2D(_MainTex, i.texcoord.xw) / 4.0h; | |||||
o += tex2D(_MainTex, i.texcoord.zw) / 4.0h; | |||||
o += tex2D(_MainTex, i.texcoord.xy) / 4.0h; | |||||
o += tex2D(_MainTex, i.texcoord.zy) / 4.0h; | |||||
return o; | |||||
} | |||||
ENDCG | |||||
SubShader | |||||
{ | |||||
Cull Off ZWrite Off ZTest Always Blend Off | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
ENDCG | |||||
} | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
//Crop before blur | |||||
#pragma vertex vertCrop | |||||
#pragma fragment frag | |||||
half4 _CropRegion; | |||||
half2 getNewUV(half2 oldUV) | |||||
{ | |||||
return lerp(_CropRegion.xy, _CropRegion.zw, oldUV); | |||||
} | |||||
v2f vertCrop(appdata_img v) | |||||
{ | |||||
v2f o = vert(v); | |||||
o.texcoord.xy = getNewUV(o.texcoord.xy); | |||||
o.texcoord.zw = getNewUV(o.texcoord.zw); | |||||
return o; | |||||
} | |||||
ENDCG | |||||
} | |||||
} | |||||
FallBack Off | |||||
} |
@ -0,0 +1,9 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 11ecd49e831fe6c4f8f68b583206390c | |||||
ShaderImporter: | |||||
externalObjects: {} | |||||
defaultTextures: [] | |||||
nonModifiableTextures: [] | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,61 @@ | |||||
Shader "Hidden/TrueShadow/ImprintPostProcess" | |||||
{ | |||||
Properties | |||||
{ | |||||
_MainTex ("Texture", 2D) = "white" {} | |||||
} | |||||
SubShader | |||||
{ | |||||
Cull Off ZWrite Off ZTest Always | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
#pragma multi_compile_local __ BLEACH | |||||
#pragma multi_compile_local __ INSET | |||||
#include "UnityCG.cginc" | |||||
struct appdata | |||||
{ | |||||
float4 vertex : POSITION; | |||||
float2 uv : TEXCOORD0; | |||||
}; | |||||
struct v2f | |||||
{ | |||||
float2 uv : TEXCOORD0; | |||||
float4 vertex : SV_POSITION; | |||||
}; | |||||
v2f vert(appdata v) | |||||
{ | |||||
v2f o; | |||||
o.vertex = UnityObjectToClipPos(v.vertex); | |||||
o.uv = v.uv; | |||||
return o; | |||||
} | |||||
sampler2D _MainTex; | |||||
half4 frag(v2f i) : SV_Target | |||||
{ | |||||
#if BLEACH | |||||
half4 color = half4(1,1,1, tex2D(_MainTex, i.uv).a); | |||||
#else | |||||
half4 color = tex2D(_MainTex, i.uv); | |||||
#endif | |||||
#if INSET | |||||
color.a = 1 - color.a; | |||||
#endif | |||||
color.rgb*=color.a; | |||||
return color; | |||||
} | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 56c6b737089145c590ee9874dc3c09f7 | |||||
timeCreated: 1602477197 |
@ -0,0 +1,59 @@ | |||||
Shader "Hidden/TrueShadow/Cutout" | |||||
{ | |||||
Properties | |||||
{ | |||||
_MainTex ("Texture", 2D) = "white" {} | |||||
_Offset ("Offset", Vector) = (0,0,0,0) | |||||
} | |||||
SubShader | |||||
{ | |||||
Cull Off ZWrite Off ZTest Always | |||||
Blend Zero OneMinusSrcAlpha, Zero OneMinusSrcAlpha | |||||
BlendOp Add, Add | |||||
// ColorMask a | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
#include "UnityCG.cginc" | |||||
struct appdata | |||||
{ | |||||
float4 vertex : POSITION; | |||||
float2 uv : TEXCOORD0; | |||||
}; | |||||
struct v2f | |||||
{ | |||||
float2 uv : TEXCOORD0; | |||||
float4 vertex : SV_POSITION; | |||||
}; | |||||
v2f vert(appdata v) | |||||
{ | |||||
v2f o; | |||||
o.vertex = UnityObjectToClipPos(v.vertex); | |||||
o.uv = v.uv; | |||||
return o; | |||||
} | |||||
uniform sampler2D _MainTex; | |||||
uniform float2 _Offset; | |||||
uniform float _OverflowAlpha; | |||||
fixed4 frag(v2f i) : SV_Target | |||||
{ | |||||
float2 uv = i.uv + _Offset; | |||||
if (any(uv > 1) || any(uv < 0)) | |||||
return _OverflowAlpha; | |||||
return fixed4(0, 0, 0, tex2D(_MainTex, uv).a); | |||||
} | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,9 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 77361e944e5710f4993a94af0720a22f | |||||
ShaderImporter: | |||||
externalObjects: {} | |||||
defaultTextures: [] | |||||
nonModifiableTextures: [] | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,69 @@ | |||||
Shader "Hidden/TrueShadow/PostProcess" | |||||
{ | |||||
Properties | |||||
{ | |||||
_MainTex ("Texture", 2D) = "white" {} | |||||
_Offset ("Offset", Vector) = (0,0,0,0) | |||||
} | |||||
SubShader | |||||
{ | |||||
Cull Off ZWrite Off ZTest Always | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
#include "UnityCG.cginc" | |||||
struct appdata | |||||
{ | |||||
float4 vertex : POSITION; | |||||
float2 uv : TEXCOORD0; | |||||
}; | |||||
struct v2f | |||||
{ | |||||
float2 uv : TEXCOORD0; | |||||
float4 vertex : SV_POSITION; | |||||
}; | |||||
v2f vert(appdata v) | |||||
{ | |||||
v2f o; | |||||
o.vertex = UnityObjectToClipPos(v.vertex); | |||||
o.uv = v.uv; | |||||
return o; | |||||
} | |||||
uniform sampler2D _ShadowTex; | |||||
uniform sampler2D _MainTex; | |||||
uniform float2 _Offset; | |||||
uniform float _OverflowAlpha; | |||||
uniform float _AlphaMultiplier; | |||||
fixed4 frag(v2f i) : SV_Target | |||||
{ | |||||
half4 shadow = tex2D(_ShadowTex, i.uv); | |||||
half alpha = shadow.a; | |||||
half alphaBoosted = saturate(alpha * _AlphaMultiplier); | |||||
shadow.a = alphaBoosted; | |||||
shadow.rgb = shadow.rgb / alpha * alphaBoosted; | |||||
float cutOut; | |||||
float2 cutoutUv = i.uv + _Offset; | |||||
if (any(cutoutUv > 1) || any(cutoutUv < 0)) | |||||
cutOut = _OverflowAlpha; | |||||
else | |||||
cutOut = tex2D(_MainTex, cutoutUv).a; | |||||
shadow *= 1 - cutOut; | |||||
return shadow; | |||||
} | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 64f3fdefd5e7411dae947574155e2253 | |||||
timeCreated: 1614411795 |
@ -0,0 +1,54 @@ | |||||
Shader "UI/TrueShadow-Additive" | |||||
{ | |||||
Properties | |||||
{ | |||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} | |||||
_StencilComp ("Stencil Comparison", Float) = 8 | |||||
_Stencil ("Stencil ID", Float) = 0 | |||||
_StencilOp ("Stencil Operation", Float) = 0 | |||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255 | |||||
_StencilReadMask ("Stencil Read Mask", Float) = 255 | |||||
_ColorMask ("Color Mask", Float) = 15 | |||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 | |||||
} | |||||
SubShader | |||||
{ | |||||
Tags | |||||
{ | |||||
"Queue"="Transparent" | |||||
"IgnoreProjector"="True" | |||||
"RenderType"="Transparent" | |||||
"PreviewType"="Plane" | |||||
"CanUseSpriteAtlas"="True" | |||||
} | |||||
Stencil | |||||
{ | |||||
Ref [_Stencil] | |||||
Comp [_StencilComp] | |||||
Pass [_StencilOp] | |||||
ReadMask [_StencilReadMask] | |||||
WriteMask [_StencilWriteMask] | |||||
} | |||||
Cull Off | |||||
Lighting Off | |||||
ZWrite Off | |||||
ZTest [unity_GUIZTestMode] | |||||
Blend One One | |||||
ColorMask [_ColorMask] | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#include "TrueShadow.cginc" | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 6c0e0b60ad64412c8cfcffd1553dcc06 | |||||
timeCreated: 1594096623 |
@ -0,0 +1,56 @@ | |||||
Shader "UI/TrueShadow-Multiply" | |||||
{ | |||||
Properties | |||||
{ | |||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} | |||||
_StencilComp ("Stencil Comparison", Float) = 8 | |||||
_Stencil ("Stencil ID", Float) = 0 | |||||
_StencilOp ("Stencil Operation", Float) = 0 | |||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255 | |||||
_StencilReadMask ("Stencil Read Mask", Float) = 255 | |||||
_ColorMask ("Color Mask", Float) = 15 | |||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 | |||||
} | |||||
SubShader | |||||
{ | |||||
Tags | |||||
{ | |||||
"Queue"="Transparent" | |||||
"IgnoreProjector"="True" | |||||
"RenderType"="Transparent" | |||||
"PreviewType"="Plane" | |||||
"CanUseSpriteAtlas"="True" | |||||
} | |||||
Stencil | |||||
{ | |||||
Ref [_Stencil] | |||||
Comp [_StencilComp] | |||||
Pass [_StencilOp] | |||||
ReadMask [_StencilReadMask] | |||||
WriteMask [_StencilWriteMask] | |||||
} | |||||
Cull Off | |||||
Lighting Off | |||||
ZWrite Off | |||||
ZTest [unity_GUIZTestMode] | |||||
Blend DstColor Zero | |||||
ColorMask [_ColorMask] | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#define DO_BLEND_POSTPROCESS color.rgb = 1 - color.a + color.rgb; | |||||
#include "TrueShadow.cginc" | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 84318cb7c8604c59a985a677d93e4048 | |||||
timeCreated: 1594096631 |
@ -0,0 +1,54 @@ | |||||
Shader "UI/TrueShadow-Normal" | |||||
{ | |||||
Properties | |||||
{ | |||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} | |||||
_StencilComp ("Stencil Comparison", Float) = 8 | |||||
_Stencil ("Stencil ID", Float) = 0 | |||||
_StencilOp ("Stencil Operation", Float) = 0 | |||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255 | |||||
_StencilReadMask ("Stencil Read Mask", Float) = 255 | |||||
_ColorMask ("Color Mask", Float) = 15 | |||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 | |||||
} | |||||
SubShader | |||||
{ | |||||
Tags | |||||
{ | |||||
"Queue"="Transparent" | |||||
"IgnoreProjector"="True" | |||||
"RenderType"="Transparent" | |||||
"PreviewType"="Plane" | |||||
"CanUseSpriteAtlas"="True" | |||||
} | |||||
Stencil | |||||
{ | |||||
Ref [_Stencil] | |||||
Comp [_StencilComp] | |||||
Pass [_StencilOp] | |||||
ReadMask [_StencilReadMask] | |||||
WriteMask [_StencilWriteMask] | |||||
} | |||||
Cull Off | |||||
Lighting Off | |||||
ZWrite Off | |||||
ZTest [unity_GUIZTestMode] | |||||
Blend One OneMinusSrcAlpha | |||||
ColorMask [_ColorMask] | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#include "TrueShadow.cginc" | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: d81692c4b57a49aabf43f3423dc99e51 | |||||
timeCreated: 1594094098 |
@ -0,0 +1,54 @@ | |||||
Shader "UI/TrueShadow-Screen" | |||||
{ | |||||
Properties | |||||
{ | |||||
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} | |||||
_StencilComp ("Stencil Comparison", Float) = 8 | |||||
_Stencil ("Stencil ID", Float) = 0 | |||||
_StencilOp ("Stencil Operation", Float) = 0 | |||||
_StencilWriteMask ("Stencil Write Mask", Float) = 255 | |||||
_StencilReadMask ("Stencil Read Mask", Float) = 255 | |||||
_ColorMask ("Color Mask", Float) = 15 | |||||
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 | |||||
} | |||||
SubShader | |||||
{ | |||||
Tags | |||||
{ | |||||
"Queue"="Transparent" | |||||
"IgnoreProjector"="True" | |||||
"RenderType"="Transparent" | |||||
"PreviewType"="Plane" | |||||
"CanUseSpriteAtlas"="True" | |||||
} | |||||
Stencil | |||||
{ | |||||
Ref [_Stencil] | |||||
Comp [_StencilComp] | |||||
Pass [_StencilOp] | |||||
ReadMask [_StencilReadMask] | |||||
WriteMask [_StencilWriteMask] | |||||
} | |||||
Cull Off | |||||
Lighting Off | |||||
ZWrite Off | |||||
ZTest [unity_GUIZTestMode] | |||||
Blend OneMinusDstColor One | |||||
ColorMask [_ColorMask] | |||||
Pass | |||||
{ | |||||
CGPROGRAM | |||||
#include "TrueShadow.cginc" | |||||
#pragma vertex vert | |||||
#pragma fragment frag | |||||
ENDCG | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: f15b379e29b64cb1ae050f22d002148a | |||||
timeCreated: 1610185734 |
@ -0,0 +1,62 @@ | |||||
#pragma target 2.0 | |||||
#include "UnityCG.cginc" | |||||
#include "UnityUI.cginc" | |||||
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT | |||||
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP | |||||
struct appdata_t | |||||
{ | |||||
float4 vertex : POSITION; | |||||
float4 color : COLOR; | |||||
float2 texcoord : TEXCOORD0; | |||||
UNITY_VERTEX_INPUT_INSTANCE_ID | |||||
}; | |||||
struct v2f | |||||
{ | |||||
float4 vertex : SV_POSITION; | |||||
fixed4 color : COLOR; | |||||
float2 texcoord : TEXCOORD0; | |||||
float4 worldPosition : TEXCOORD1; | |||||
UNITY_VERTEX_OUTPUT_STEREO | |||||
}; | |||||
sampler2D _MainTex; | |||||
fixed4 _TextureSampleAdd; | |||||
float4 _ClipRect; | |||||
float4 _MainTex_ST; | |||||
v2f vert(appdata_t v) | |||||
{ | |||||
v2f OUT; | |||||
UNITY_SETUP_INSTANCE_ID(v); | |||||
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); | |||||
OUT.worldPosition = v.vertex; | |||||
OUT.vertex = UnityObjectToClipPos(OUT.worldPosition); | |||||
OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); | |||||
OUT.color.a = v.color.a; | |||||
OUT.color.rgb = v.color.rgb * v.color.a; | |||||
return OUT; | |||||
} | |||||
fixed4 frag(v2f IN) : SV_Target | |||||
{ | |||||
float4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color; | |||||
#ifdef UNITY_UI_CLIP_RECT | |||||
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect); | |||||
#endif | |||||
#ifdef UNITY_UI_ALPHACLIP | |||||
clip (color.a - 0.001); | |||||
#endif | |||||
#ifdef DO_BLEND_POSTPROCESS | |||||
DO_BLEND_POSTPROCESS | |||||
#endif | |||||
return color; | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 3521747ea3d847a58d919a5a4d9f0fc6 | |||||
timeCreated: 1594096488 |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: adf483f749616b14ab22fc1bac9a154e | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,78 @@ | |||||
#if LETAI_TRUESHADOW_DEBUG | |||||
using System; | |||||
using UnityEngine; | |||||
#if UNITY_EDITOR | |||||
using System.IO; | |||||
using UnityEditor; | |||||
#endif | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
[Serializable] | |||||
public class DebugSettings | |||||
{ | |||||
const string FILE_PATH = "ProjectSettings/TrueShadowDebugSettings.json"; | |||||
static DebugSettings instance; | |||||
public static DebugSettings Instance | |||||
{ | |||||
get | |||||
{ | |||||
if (instance == null) | |||||
{ | |||||
#if UNITY_EDITOR | |||||
try | |||||
{ | |||||
if (File.Exists(FILE_PATH)) | |||||
instance = JsonUtility.FromJson<DebugSettings>(File.ReadAllText(FILE_PATH)); | |||||
else | |||||
Create(); | |||||
} | |||||
catch (Exception) | |||||
{ | |||||
Create(); | |||||
} | |||||
#else | |||||
Create(); | |||||
#endif | |||||
} | |||||
return instance; | |||||
} | |||||
} | |||||
static void Create() | |||||
{ | |||||
instance = new DebugSettings(); | |||||
instance.Save(); | |||||
} | |||||
public bool showObjects = true; | |||||
void Save() | |||||
{ | |||||
#if UNITY_EDITOR | |||||
File.WriteAllText(FILE_PATH, JsonUtility.ToJson(this, true)); | |||||
#endif | |||||
} | |||||
#if UNITY_EDITOR | |||||
[MenuItem("Tools/Show Objects")] | |||||
static void ShowObjects() | |||||
{ | |||||
Instance.showObjects = true; | |||||
Instance.Save(); | |||||
} | |||||
[MenuItem("Tools/Hide Objects")] | |||||
static void HideObjects() | |||||
{ | |||||
Instance.showObjects = false; | |||||
Instance.Save(); | |||||
} | |||||
#endif | |||||
} | |||||
} | |||||
#endif |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 15f8726536514604185aa6c94cb75b8b | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 711abe41546958b459a552baad798f17 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,33 @@ | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using UnityEditor; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[InitializeOnLoad] | |||||
public static class AutoCustomScriptingDefine | |||||
{ | |||||
internal static readonly HashSet<string> SYMBOLS = new HashSet<string> {"LETAI_TRUESHADOW"}; | |||||
static AutoCustomScriptingDefine() | |||||
{ | |||||
Apply(); | |||||
} | |||||
public static void Apply() | |||||
{ | |||||
AddMissingSymbols(EditorUserBuildSettings.activeBuildTarget); | |||||
} | |||||
static void AddMissingSymbols(BuildTarget buildTarget) | |||||
{ | |||||
var currentGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); | |||||
var defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(currentGroup).Split(';').ToList(); | |||||
var missing = SYMBOLS.Except(defines).ToList(); | |||||
defines.AddRange(missing); | |||||
if (missing.Count > 0) | |||||
PlayerSettings.SetScriptingDefineSymbolsForGroup(currentGroup, string.Join(";", defines)); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 150508a406864b6ca9ad4e38e75cb333 | |||||
timeCreated: 1597477199 |
@ -0,0 +1,59 @@ | |||||
using System; | |||||
using System.Globalization; | |||||
using System.Reflection; | |||||
using UnityEditor; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
public class EditorProperty | |||||
{ | |||||
public readonly SerializedProperty serializedProperty; | |||||
readonly SerializedObject serializedObject; | |||||
readonly MethodInfo propertySetter; | |||||
readonly SerializedProperty dirtyFlag; | |||||
public EditorProperty(SerializedObject obj, string name) | |||||
{ | |||||
var propertyName = char.ToLowerInvariant(name[0]) + name.Substring(1); | |||||
serializedObject = obj; | |||||
serializedProperty = serializedObject.FindProperty(propertyName); | |||||
propertySetter = serializedObject.targetObject.GetType().GetProperty(name).SetMethod; | |||||
dirtyFlag = serializedObject.FindProperty("modifiedFromInspector"); | |||||
} | |||||
public void Draw() | |||||
{ | |||||
using (var scope = new EditorGUI.ChangeCheckScope()) | |||||
{ | |||||
EditorGUILayout.PropertyField(serializedProperty); | |||||
if (!scope.changed) | |||||
return; | |||||
dirtyFlag.boolValue = true; | |||||
serializedObject.ApplyModifiedProperties(); | |||||
foreach (var target in serializedObject.targetObjects) | |||||
{ | |||||
switch (serializedProperty.propertyType) | |||||
{ | |||||
case SerializedPropertyType.Float: | |||||
propertySetter.Invoke(target, new object[] {serializedProperty.floatValue}); | |||||
break; | |||||
case SerializedPropertyType.Enum: | |||||
propertySetter.Invoke(target, new object[] {serializedProperty.enumValueIndex}); | |||||
break; | |||||
case SerializedPropertyType.Boolean: | |||||
propertySetter.Invoke(target, new object[] {serializedProperty.boolValue}); | |||||
break; | |||||
case SerializedPropertyType.Color: | |||||
propertySetter.Invoke(target, new object[] {serializedProperty.colorValue}); | |||||
break; | |||||
default: throw new NotImplementedException(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 1f9db8b2544c48c2a7f13b46f27236e8 | |||||
timeCreated: 1594635819 |
@ -0,0 +1,67 @@ | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[CustomPropertyDrawer(typeof(InsetToggleAttribute))] | |||||
public class InsetToggle : InlineToolbar | |||||
{ | |||||
static readonly Texture OUTER_SHADOW_TEXTURE = Utility.FindAsset<Texture>("Outer Shadow"); | |||||
static readonly Texture INNER_SHADOW_TEXTURE = Utility.FindAsset<Texture>("Inner Shadow"); | |||||
static InsetToggle() | |||||
{ | |||||
textures = new[] {OUTER_SHADOW_TEXTURE, INNER_SHADOW_TEXTURE}; | |||||
} | |||||
} | |||||
public class InlineToolbar : PropertyDrawer | |||||
{ | |||||
protected static Texture[] textures; | |||||
static readonly GUIStyle LABEL_STYLE = new GUIStyle(EditorStyles.label) | |||||
{alignment = TextAnchor.MiddleLeft,}; | |||||
public override void OnGUI(Rect position, SerializedProperty property, | |||||
GUIContent label) | |||||
{ | |||||
using (var propScope = new EditorGUI.PropertyScope(position, label, property)) | |||||
{ | |||||
int id = GUIUtility.GetControlID(FocusType.Keyboard, position); | |||||
var lableRect = position; | |||||
lableRect.y += (lableRect.height - EditorGUIUtility.singleLineHeight) / 2; | |||||
lableRect.height = EditorGUIUtility.singleLineHeight; | |||||
var toolbarRect = EditorGUI.PrefixLabel(lableRect, id, propScope.content, LABEL_STYLE); | |||||
toolbarRect.width = EditorGUIUtility.singleLineHeight * 4f; | |||||
toolbarRect.height = position.height; | |||||
toolbarRect.y = position.y; | |||||
using (var changeScope = new EditorGUI.ChangeCheckScope()) | |||||
{ | |||||
var isOn = GUI.Toolbar(toolbarRect, property.boolValue ? 1 : 0, textures) == 1; | |||||
var changed = changeScope.changed; | |||||
if (Event.current.type == EventType.KeyDown && | |||||
GUIUtility.keyboardControl == id) | |||||
{ | |||||
if (Event.current.keyCode == KeyCode.Return || | |||||
Event.current.keyCode == KeyCode.KeypadEnter || | |||||
Event.current.keyCode == KeyCode.Space) | |||||
{ | |||||
changed = GUI.changed = true; | |||||
isOn = !isOn; | |||||
} | |||||
} | |||||
if (changed) | |||||
property.boolValue = isOn; | |||||
} | |||||
} | |||||
} | |||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |||||
{ | |||||
return EditorGUIUtility.singleLineHeight * 2f; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: ea4346ac17c04991bf233f1dc0def012 | |||||
timeCreated: 1602580020 |
@ -0,0 +1,184 @@ | |||||
using System; | |||||
using System.Reflection; | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
using static LeTai.TrueShadow.Math; | |||||
using EGU = UnityEditor.EditorGUIUtility; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[CustomPropertyDrawer(typeof(KnobAttribute))] | |||||
public class KnobPropertyDrawer : PropertyDrawer | |||||
{ | |||||
public static bool procrastinationMode = false; | |||||
static readonly Texture2D KNOB_BG_TEXTURE = Utility.FindAsset<Texture2D>("Knob_BG"); | |||||
static readonly Texture2D KNOB_FG_TEXTURE = Utility.FindAsset<Texture2D>("Knob_FG"); | |||||
static readonly MethodInfo DO_FLOAT_FIELD_METHOD; | |||||
static readonly FieldInfo RECYCLED_EDITOR_PROPERTY; | |||||
static readonly FieldInfo FLOAT_FIELD_FORMAT_STRING_CONST; | |||||
static readonly Color KNOB_BG_COLOR; | |||||
static readonly Color KNOB_FG_COLOR; | |||||
static readonly Color KNOB_FG_COLOR_ACTIVE; | |||||
static KnobPropertyDrawer() | |||||
{ | |||||
var editorGUIType = typeof(EditorGUI); | |||||
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static; | |||||
Type[] argumentTypes = { | |||||
Assembly.GetAssembly(editorGUIType).GetType("UnityEditor.EditorGUI+RecycledTextEditor"), | |||||
typeof(Rect), | |||||
typeof(Rect), | |||||
typeof(int), | |||||
typeof(float), | |||||
typeof(string), | |||||
typeof(GUIStyle), | |||||
typeof(bool) | |||||
}; | |||||
DO_FLOAT_FIELD_METHOD = editorGUIType.GetMethod("DoFloatField", flags, null, argumentTypes, null); | |||||
RECYCLED_EDITOR_PROPERTY = editorGUIType.GetField("s_RecycledEditor", flags); | |||||
FLOAT_FIELD_FORMAT_STRING_CONST = editorGUIType.GetField("kFloatFieldFormatString", flags); | |||||
if (EGU.isProSkin) | |||||
{ | |||||
KNOB_BG_COLOR = new Color(.164f, .164f, .164f); | |||||
KNOB_FG_COLOR = new Color(.701f, .701f, .701f); | |||||
KNOB_FG_COLOR_ACTIVE = new Color(.49f, .67f, .94f); | |||||
} | |||||
else | |||||
{ | |||||
KNOB_BG_COLOR = new Color(.941f, .941f, .941f); | |||||
KNOB_FG_COLOR = new Color(.239f, .239f, .239f); | |||||
KNOB_FG_COLOR_ACTIVE = new Color(.054f, .274f, .549f); | |||||
} | |||||
} | |||||
static float DoFloatFieldInternal(Rect position, | |||||
Rect dragHotZone, | |||||
int id, | |||||
float value, | |||||
string formatString = null, | |||||
GUIStyle style = null, | |||||
bool draggable = true) | |||||
{ | |||||
style = style ?? EditorStyles.numberField; | |||||
formatString = formatString ?? (string) FLOAT_FIELD_FORMAT_STRING_CONST.GetValue(null); | |||||
var editor = RECYCLED_EDITOR_PROPERTY.GetValue(null); | |||||
return (float) DO_FLOAT_FIELD_METHOD.Invoke(null, new[] { | |||||
editor, | |||||
position, | |||||
dragHotZone, | |||||
id, | |||||
value, | |||||
formatString, | |||||
style, | |||||
draggable | |||||
}); | |||||
} | |||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |||||
{ | |||||
if (!(attribute is KnobAttribute)) return; | |||||
KnobProperty(position, label, property, Vector2.right); | |||||
} | |||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | |||||
{ | |||||
return ControlHeight; | |||||
} | |||||
static float ControlHeight => EGU.singleLineHeight * 2.0f; | |||||
static float KnobSize => EGU.singleLineHeight * 2.5f; | |||||
static float KnobYOffset => (ControlHeight - KnobSize) / 2; | |||||
static Color Lighten(Color color, float amount) | |||||
{ | |||||
Color.RGBToHSV(color, out var h, out var s, out var v); | |||||
return Color.HSVToRGB(h, s, v + amount); | |||||
} | |||||
public static void KnobProperty(Rect rect, GUIContent label, SerializedProperty prop, Vector2 zeroVector) | |||||
{ | |||||
float angle = prop.floatValue; | |||||
float prevAngle = angle; | |||||
using (var propScope = new EditorGUI.PropertyScope(rect, label, prop)) | |||||
using (var changeScope = new EditorGUI.ChangeCheckScope()) | |||||
{ | |||||
var labelRect = new Rect(rect) { | |||||
y = rect.y + (ControlHeight - EGU.singleLineHeight) / 2, | |||||
height = EGU.singleLineHeight | |||||
}; | |||||
int fieldId = GUIUtility.GetControlID(FocusType.Keyboard, labelRect); | |||||
var fieldRect = EditorGUI.PrefixLabel(labelRect, fieldId, propScope.content); | |||||
labelRect.xMax = fieldRect.x; | |||||
fieldRect.x += ControlHeight; | |||||
fieldRect.width -= ControlHeight; | |||||
Rect knobRect = new Rect(rect.x + EGU.labelWidth + KnobYOffset, | |||||
rect.y + KnobYOffset, | |||||
KnobSize, KnobSize); | |||||
int knobId = GUIUtility.GetControlID(FocusType.Passive, knobRect); | |||||
if (Event.current != null) | |||||
{ | |||||
if (Event.current.type == EventType.MouseDown && knobRect.Contains(Event.current.mousePosition)) | |||||
{ | |||||
GUIUtility.hotControl = knobId; | |||||
angle = Angle360(zeroVector, Event.current.mousePosition - knobRect.center); | |||||
} | |||||
else if (Event.current.type == EventType.MouseUp && GUIUtility.hotControl == knobId) | |||||
{ | |||||
GUIUtility.hotControl = 0; | |||||
} | |||||
else if (Event.current.type == EventType.MouseDrag && GUIUtility.hotControl == knobId) | |||||
{ | |||||
angle = Angle360(zeroVector, Event.current.mousePosition - knobRect.center); | |||||
} | |||||
else if (Event.current.type == EventType.Repaint) | |||||
{ | |||||
var notRotated = GUI.matrix; | |||||
var oldColor = GUI.color; | |||||
var highlighted = GUIUtility.hotControl == knobId || | |||||
GUIUtility.hotControl == fieldId || | |||||
GUIUtility.keyboardControl == fieldId; | |||||
GUIUtility.RotateAroundPivot(angle, knobRect.center); | |||||
GUI.color = KNOB_BG_COLOR; | |||||
GUI.DrawTexture(knobRect, KNOB_BG_TEXTURE, ScaleMode.ScaleToFit, true, 1); | |||||
GUI.color = highlighted ? KNOB_FG_COLOR_ACTIVE : KNOB_FG_COLOR; | |||||
if (procrastinationMode) GUI.color = Color.red; | |||||
GUI.DrawTexture(knobRect, KNOB_FG_TEXTURE, ScaleMode.ScaleToFit, true, 1); | |||||
if (!procrastinationMode) | |||||
GUI.matrix = notRotated; | |||||
GUI.color = oldColor; | |||||
} | |||||
if (angle != prevAngle) GUI.changed = true; | |||||
} | |||||
angle = DoFloatFieldInternal( | |||||
fieldRect, | |||||
labelRect, | |||||
fieldId, | |||||
angle | |||||
); | |||||
if (changeScope.changed) prop.floatValue = angle; | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 07c24ad14ec14d40ab304c80ba6af657 | |||||
timeCreated: 1594636852 |
@ -0,0 +1,16 @@ | |||||
{ | |||||
"name": "LeTai.TrueShadow.Editor", | |||||
"references": [ | |||||
"LeTai.TrueShadow" | |||||
], | |||||
"optionalUnityReferences": [], | |||||
"includePlatforms": [ | |||||
"Editor" | |||||
], | |||||
"excludePlatforms": [], | |||||
"allowUnsafeCode": false, | |||||
"overrideReferences": false, | |||||
"precompiledReferences": [], | |||||
"autoReferenced": true, | |||||
"defineConstraints": [] | |||||
} |
@ -0,0 +1,7 @@ | |||||
fileFormatVersion: 2 | |||||
guid: a3c0f23110aac1b4384b9377b63b6595 | |||||
AssemblyDefinitionImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,104 @@ | |||||
using System.Linq; | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
public class MigrateToVV1Window : EditorWindow | |||||
{ | |||||
private const string SHOW_ON_START_EDITOR_PREFS_KEY = "LeTai.TrueShadow.MigrateToVV1WindowShown"; | |||||
[MenuItem("Tools/TrueShadow/Migrate To v1")] | |||||
public static MigrateToVV1Window ShowWindow() | |||||
{ | |||||
var window = GetWindow<MigrateToVV1Window>(true, "True Shadow"); | |||||
window.position = new Rect(600, 400, 600, 400); | |||||
return window; | |||||
} | |||||
[InitializeOnLoadMethod] | |||||
private static void InitializeOnLoadMethod() | |||||
{ | |||||
RegisterWindowCheck(); | |||||
} | |||||
private static void RegisterWindowCheck() | |||||
{ | |||||
if (!EditorApplication.isPlayingOrWillChangePlaymode) | |||||
{ | |||||
EditorApplication.update += CheckShowWindow; | |||||
} | |||||
} | |||||
private static void CheckShowWindow() | |||||
{ | |||||
EditorApplication.update -= CheckShowWindow; | |||||
if (EditorPrefs.GetBool(SHOW_ON_START_EDITOR_PREFS_KEY, true)) | |||||
{ | |||||
ShowWindow(); | |||||
} | |||||
} | |||||
void OnDestroy() | |||||
{ | |||||
EditorPrefs.SetBool(SHOW_ON_START_EDITOR_PREFS_KEY, false); | |||||
} | |||||
bool haveBackup; | |||||
private void OnGUI() | |||||
{ | |||||
GUILayout.Label("Migrate to v1", EditorStyles.largeLabel); | |||||
EditorGUILayout.Separator(); | |||||
EditorGUILayout.HelpBox( | |||||
"In v1, Blend Mode was changed to produce better looking shadows, as well as better compatibility with 3rd parties asset. As a side effect, most shadows should now use Color Bleed Mode: <Black>. This tool attempt to do this automatically.\n\n" + | |||||
"All True Shadows in currently loaded scenes will be migrated. You may want to load all scenes you want to fix before migrating. All True Shadows in prefabs will also be migrated.\n\n" + | |||||
"All True Shadows in prefabs will also be migrated.\n\n" + | |||||
"You may access this dialog later from the Tools menu.", | |||||
MessageType.Info | |||||
); | |||||
EditorGUILayout.Separator(); | |||||
EditorGUILayout.HelpBox( | |||||
"!!! MAKE SURE TO BACK UP YOUR PROJECT BEFORE USE !!!\n\n" + | |||||
"This tool will modify your project files. Please backup your project before use. If you are unsure how to do this, do NOT use this tool! Manually change any problematic shadows Color Bleed mode to Black instead!", | |||||
MessageType.Warning); | |||||
EditorGUILayout.Separator(); | |||||
haveBackup = EditorGUILayout.ToggleLeft("I have backed up the project and can undo any changes done by the tool", haveBackup); | |||||
if (haveBackup) | |||||
{ | |||||
if (GUILayout.Button("Migrate to v1")) | |||||
MigrateToV1(); | |||||
} | |||||
} | |||||
public static void MigrateToV1() | |||||
{ | |||||
var allPrefabs = AssetDatabase.FindAssets("t:Prefab"); | |||||
foreach (var guid in allPrefabs) | |||||
{ | |||||
var path = AssetDatabase.GUIDToAssetPath(guid); | |||||
var prefabRoot = PrefabUtility.LoadPrefabContents(path); | |||||
var changed = false; | |||||
foreach (var shadow in prefabRoot.GetComponentsInChildren<TrueShadow>()) | |||||
{ | |||||
shadow.ColorBleedMode = ColorBleedMode.Black; | |||||
changed = true; | |||||
} | |||||
if (changed) | |||||
PrefabUtility.SaveAsPrefabAsset(prefabRoot, path); | |||||
PrefabUtility.UnloadPrefabContents(prefabRoot); | |||||
} | |||||
var inScene = Resources.FindObjectsOfTypeAll<TrueShadow>() | |||||
.ToArray(); | |||||
Undo.RecordObjects(inScene, "Migrate to 0.5"); | |||||
foreach (var shadow in inScene) | |||||
{ | |||||
shadow.ColorBleedMode = ColorBleedMode.Black; | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 126149305ac84024ac1d713f39caf754 | |||||
timeCreated: 1611560835 |
@ -0,0 +1,19 @@ | |||||
using UnityEditor; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[InitializeOnLoad] | |||||
class PrefabEventHandler | |||||
{ | |||||
static PrefabEventHandler() | |||||
{ | |||||
PrefabUtility.prefabInstanceUpdated += go => | |||||
{ | |||||
var shadows = go.GetComponentsInChildren<TrueShadow>(); | |||||
foreach (var shadow in shadows) | |||||
shadow.ApplySerializedData(); | |||||
}; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 9806fc5a3b18445e9766a68cf69b81c9 | |||||
timeCreated: 1600845150 |
@ -0,0 +1,62 @@ | |||||
using System; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
namespace LeTai.Asset.TrueShadow.Editor | |||||
{ | |||||
public static class ScenceGizmoAutoDisable | |||||
{ | |||||
static readonly string[] NO_GIZMOS_CLASSES = { | |||||
"TrueShadow" | |||||
}; | |||||
[UnityEditor.Callbacks.DidReloadScripts] | |||||
private static void OnScriptsReloaded() | |||||
{ | |||||
var Annotation = Type.GetType("UnityEditor.Annotation, UnityEditor"); | |||||
if (Annotation == null) return; | |||||
var ClassId = Annotation.GetField("classID"); | |||||
var ScriptClass = Annotation.GetField("scriptClass"); | |||||
var Flags = Annotation.GetField("flags"); | |||||
var IconEnabled = Annotation.GetField("iconEnabled"); | |||||
Type AnnotationUtility = Type.GetType("UnityEditor.AnnotationUtility, UnityEditor"); | |||||
if (AnnotationUtility == null) return; | |||||
var GetAnnotations = AnnotationUtility.GetMethod("GetAnnotations", | |||||
BindingFlags.NonPublic | BindingFlags.Public | | |||||
BindingFlags.Static); | |||||
if (GetAnnotations == null) return; | |||||
var SetIconEnabled = AnnotationUtility.GetMethod("SetIconEnabled", | |||||
BindingFlags.NonPublic | BindingFlags.Public | | |||||
BindingFlags.Static); | |||||
if (SetIconEnabled == null) return; | |||||
Array annotations = (Array) GetAnnotations.Invoke(null, null); | |||||
foreach (var a in annotations) | |||||
{ | |||||
int classId = (int) ClassId.GetValue(a); | |||||
string scriptClass = (string) ScriptClass.GetValue(a); | |||||
int flags = (int) Flags.GetValue(a); | |||||
int iconEnabled = (int) IconEnabled.GetValue(a); | |||||
// built in types | |||||
if (string.IsNullOrEmpty(scriptClass)) continue; | |||||
// load a json or text file with class names | |||||
const int HasIcon = 1; | |||||
bool hasIconFlag = (flags & HasIcon) == HasIcon; | |||||
if (hasIconFlag && | |||||
iconEnabled != 0 && | |||||
NO_GIZMOS_CLASSES.Contains(scriptClass)) | |||||
{ | |||||
SetIconEnabled.Invoke(null, new object[] {classId, scriptClass, 0}); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: a1266d64cf1066b47b900e5115a5b75d | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,87 @@ | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
using static UnityEditor.EditorGUI; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[CustomPropertyDrawer(typeof(SpreadSliderAttribute))] | |||||
public class SpreadSliderDrawer : PropertyDrawer | |||||
{ | |||||
const float SLIDER_SPACING = 5; | |||||
const float MARKER_HEIGHT = 6; | |||||
const float MARKER_ALPHA = .75f; | |||||
const float MARKER_FILLET = 2; | |||||
static readonly Vector4 START_RADII = new Vector4(MARKER_FILLET, 0, 0, MARKER_FILLET); | |||||
static readonly Vector4 END_RADII = new Vector4(0, MARKER_FILLET, MARKER_FILLET, 0); | |||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | |||||
{ | |||||
using (var propScope = new PropertyScope(position, label, property)) | |||||
using (var changeScope = new ChangeCheckScope()) | |||||
{ | |||||
var controlPosition = PrefixLabel(position, propScope.content); | |||||
var floatFieldWidth = Mathf.Min(EditorGUIUtility.fieldWidth, controlPosition.width); | |||||
var sliderPosition = new Rect(controlPosition) | |||||
{width = controlPosition.width - floatFieldWidth - SLIDER_SPACING}; | |||||
const float marker1 = .8f; | |||||
const float marker2 = .95f; | |||||
DrawMarkers(sliderPosition, | |||||
(marker1, new Color(1.00000f, 0.60392f, 0.01961f, MARKER_ALPHA)), | |||||
(marker2, new Color(1.00000f, 0.25490f, 0.20784f, MARKER_ALPHA))); | |||||
var newVal = Slider(controlPosition, | |||||
GUIContent.none, | |||||
property.floatValue, | |||||
0, 1); | |||||
if (!Event.current.control && !Event.current.alt) | |||||
{ | |||||
var dist1 = (newVal - marker1) * sliderPosition.width; | |||||
var dist2 = (newVal - marker2) * sliderPosition.width; | |||||
if (0 < dist1 && dist1 < 4) | |||||
newVal = marker1; | |||||
if (0 < dist2 && dist2 < 4) | |||||
newVal = marker2; | |||||
} | |||||
if (changeScope.changed) | |||||
property.floatValue = newVal; | |||||
} | |||||
} | |||||
void DrawMarkers(Rect sliderPosition, params (float, Color)[] markers) | |||||
{ | |||||
var hPad = GUI.skin.horizontalSliderThumb.fixedWidth / 2f; | |||||
var markerXStart = sliderPosition.x + hPad; | |||||
var markerXEnd = sliderPosition.width - hPad * 2; | |||||
var vPad = (sliderPosition.height - MARKER_HEIGHT) / 2f; | |||||
var markerYStart = sliderPosition.y + vPad; | |||||
var markerHeight = sliderPosition.height - vPad * 2; | |||||
for (var i = 0; i < markers.Length; i++) | |||||
{ | |||||
var (offset, color) = markers[i]; | |||||
var x = markerXStart + markerXEnd * offset; | |||||
var width = i < markers.Length - 1 | |||||
? sliderPosition.width * (markers[i + 1].Item1 - offset) - 1 | |||||
: sliderPosition.xMax - x; | |||||
var position = new Rect { | |||||
x = x, | |||||
y = markerYStart, | |||||
width = width, | |||||
height = markerHeight | |||||
}; | |||||
var radii = i == 0 ? START_RADII : END_RADII; | |||||
GUI.DrawTexture(position, | |||||
Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, color, | |||||
Vector4.zero, radii); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 3292983b33504803ae542a485f446019 | |||||
timeCreated: 1615190623 |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 1cd90590774134c4ea35245f256f89e9 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,130 @@ | |||||
fileFormatVersion: 2 | |||||
guid: b11fa2df94e30d244aadf3846cb15aeb | |||||
labels: | |||||
- TrueShadowEditorResources | |||||
TextureImporter: | |||||
internalIDToNameTable: [] | |||||
externalObjects: {} | |||||
serializedVersion: 11 | |||||
mipmaps: | |||||
mipMapMode: 1 | |||||
enableMipMap: 1 | |||||
sRGBTexture: 1 | |||||
linearTexture: 0 | |||||
fadeOut: 0 | |||||
borderMipMap: 0 | |||||
mipMapsPreserveCoverage: 0 | |||||
alphaTestReferenceValue: 0.5 | |||||
mipMapFadeDistanceStart: 1 | |||||
mipMapFadeDistanceEnd: 3 | |||||
bumpmap: | |||||
convertToNormalMap: 0 | |||||
externalNormalMap: 0 | |||||
heightScale: 0.25 | |||||
normalMapFilter: 0 | |||||
isReadable: 0 | |||||
streamingMipmaps: 0 | |||||
streamingMipmapsPriority: 0 | |||||
grayScaleToAlpha: 0 | |||||
generateCubemap: 6 | |||||
cubemapConvolution: 0 | |||||
seamlessCubemap: 0 | |||||
textureFormat: 1 | |||||
maxTextureSize: 2048 | |||||
textureSettings: | |||||
serializedVersion: 2 | |||||
filterMode: -1 | |||||
aniso: 1 | |||||
mipBias: -100 | |||||
wrapU: 1 | |||||
wrapV: 1 | |||||
wrapW: -1 | |||||
nPOTScale: 0 | |||||
lightmap: 0 | |||||
compressionQuality: 50 | |||||
spriteMode: 1 | |||||
spriteExtrude: 1 | |||||
spriteMeshType: 1 | |||||
alignment: 0 | |||||
spritePivot: {x: 0.5, y: 0.5} | |||||
spritePixelsToUnits: 100 | |||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||||
spriteGenerateFallbackPhysicsShape: 1 | |||||
alphaUsage: 1 | |||||
alphaIsTransparency: 1 | |||||
spriteTessellationDetail: -1 | |||||
textureType: 2 | |||||
textureShape: 1 | |||||
singleChannelComponent: 0 | |||||
maxTextureSizeSet: 0 | |||||
compressionQualitySet: 0 | |||||
textureFormatSet: 0 | |||||
applyGammaDecoding: 0 | |||||
platformSettings: | |||||
- serializedVersion: 3 | |||||
buildTarget: DefaultTexturePlatform | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Standalone | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Android | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: WebGL | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
spriteSheet: | |||||
serializedVersion: 2 | |||||
sprites: [] | |||||
outline: [] | |||||
physicsShape: [] | |||||
bones: [] | |||||
spriteID: 21fab31b34964614680dcb53be2692d1 | |||||
internalID: 0 | |||||
vertices: [] | |||||
indices: | |||||
edges: [] | |||||
weights: [] | |||||
secondaryTextures: [] | |||||
spritePackingTag: | |||||
pSDRemoveMatte: 0 | |||||
pSDShowRemoveMatteOption: 0 | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,130 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 413bbe8f3993d194682180c4acfc0d1e | |||||
labels: | |||||
- TrueShadowEditorResources | |||||
TextureImporter: | |||||
internalIDToNameTable: [] | |||||
externalObjects: {} | |||||
serializedVersion: 11 | |||||
mipmaps: | |||||
mipMapMode: 0 | |||||
enableMipMap: 1 | |||||
sRGBTexture: 1 | |||||
linearTexture: 0 | |||||
fadeOut: 0 | |||||
borderMipMap: 0 | |||||
mipMapsPreserveCoverage: 0 | |||||
alphaTestReferenceValue: 0.5 | |||||
mipMapFadeDistanceStart: 1 | |||||
mipMapFadeDistanceEnd: 3 | |||||
bumpmap: | |||||
convertToNormalMap: 0 | |||||
externalNormalMap: 0 | |||||
heightScale: 0.25 | |||||
normalMapFilter: 0 | |||||
isReadable: 0 | |||||
streamingMipmaps: 0 | |||||
streamingMipmapsPriority: 0 | |||||
grayScaleToAlpha: 0 | |||||
generateCubemap: 6 | |||||
cubemapConvolution: 0 | |||||
seamlessCubemap: 0 | |||||
textureFormat: 1 | |||||
maxTextureSize: 2048 | |||||
textureSettings: | |||||
serializedVersion: 2 | |||||
filterMode: 2 | |||||
aniso: 16 | |||||
mipBias: -100 | |||||
wrapU: 1 | |||||
wrapV: 1 | |||||
wrapW: -1 | |||||
nPOTScale: 0 | |||||
lightmap: 0 | |||||
compressionQuality: 50 | |||||
spriteMode: 1 | |||||
spriteExtrude: 1 | |||||
spriteMeshType: 1 | |||||
alignment: 0 | |||||
spritePivot: {x: 0.5, y: 0.5} | |||||
spritePixelsToUnits: 100 | |||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||||
spriteGenerateFallbackPhysicsShape: 1 | |||||
alphaUsage: 1 | |||||
alphaIsTransparency: 1 | |||||
spriteTessellationDetail: -1 | |||||
textureType: 2 | |||||
textureShape: 1 | |||||
singleChannelComponent: 0 | |||||
maxTextureSizeSet: 0 | |||||
compressionQualitySet: 0 | |||||
textureFormatSet: 0 | |||||
applyGammaDecoding: 1 | |||||
platformSettings: | |||||
- serializedVersion: 3 | |||||
buildTarget: DefaultTexturePlatform | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Standalone | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Android | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: WebGL | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
spriteSheet: | |||||
serializedVersion: 2 | |||||
sprites: [] | |||||
outline: [] | |||||
physicsShape: [] | |||||
bones: [] | |||||
spriteID: 21fab31b34964614680dcb53be2692d1 | |||||
internalID: 0 | |||||
vertices: [] | |||||
indices: | |||||
edges: [] | |||||
weights: [] | |||||
secondaryTextures: [] | |||||
spritePackingTag: | |||||
pSDRemoveMatte: 0 | |||||
pSDShowRemoveMatteOption: 0 | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,130 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 94d408ca95ce77245aa4866768b6b55c | |||||
labels: | |||||
- TrueShadowEditorResources | |||||
TextureImporter: | |||||
internalIDToNameTable: [] | |||||
externalObjects: {} | |||||
serializedVersion: 11 | |||||
mipmaps: | |||||
mipMapMode: 0 | |||||
enableMipMap: 1 | |||||
sRGBTexture: 1 | |||||
linearTexture: 0 | |||||
fadeOut: 0 | |||||
borderMipMap: 0 | |||||
mipMapsPreserveCoverage: 0 | |||||
alphaTestReferenceValue: 0.5 | |||||
mipMapFadeDistanceStart: 1 | |||||
mipMapFadeDistanceEnd: 3 | |||||
bumpmap: | |||||
convertToNormalMap: 0 | |||||
externalNormalMap: 0 | |||||
heightScale: 0.25 | |||||
normalMapFilter: 0 | |||||
isReadable: 0 | |||||
streamingMipmaps: 0 | |||||
streamingMipmapsPriority: 0 | |||||
grayScaleToAlpha: 0 | |||||
generateCubemap: 6 | |||||
cubemapConvolution: 0 | |||||
seamlessCubemap: 0 | |||||
textureFormat: 1 | |||||
maxTextureSize: 2048 | |||||
textureSettings: | |||||
serializedVersion: 2 | |||||
filterMode: 2 | |||||
aniso: 16 | |||||
mipBias: -100 | |||||
wrapU: 1 | |||||
wrapV: 1 | |||||
wrapW: -1 | |||||
nPOTScale: 0 | |||||
lightmap: 0 | |||||
compressionQuality: 50 | |||||
spriteMode: 1 | |||||
spriteExtrude: 1 | |||||
spriteMeshType: 1 | |||||
alignment: 0 | |||||
spritePivot: {x: 0.5, y: 0.5} | |||||
spritePixelsToUnits: 100 | |||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||||
spriteGenerateFallbackPhysicsShape: 1 | |||||
alphaUsage: 1 | |||||
alphaIsTransparency: 1 | |||||
spriteTessellationDetail: -1 | |||||
textureType: 2 | |||||
textureShape: 1 | |||||
singleChannelComponent: 0 | |||||
maxTextureSizeSet: 0 | |||||
compressionQualitySet: 0 | |||||
textureFormatSet: 0 | |||||
applyGammaDecoding: 1 | |||||
platformSettings: | |||||
- serializedVersion: 3 | |||||
buildTarget: DefaultTexturePlatform | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Standalone | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Android | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: WebGL | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 0 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
spriteSheet: | |||||
serializedVersion: 2 | |||||
sprites: [] | |||||
outline: [] | |||||
physicsShape: [] | |||||
bones: [] | |||||
spriteID: 21fab31b34964614680dcb53be2692d1 | |||||
internalID: 0 | |||||
vertices: [] | |||||
indices: | |||||
edges: [] | |||||
weights: [] | |||||
secondaryTextures: [] | |||||
spritePackingTag: | |||||
pSDRemoveMatte: 0 | |||||
pSDShowRemoveMatteOption: 0 | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,130 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 8cad3feef3766b240a76d467e152152e | |||||
labels: | |||||
- TrueShadowEditorResources | |||||
TextureImporter: | |||||
internalIDToNameTable: [] | |||||
externalObjects: {} | |||||
serializedVersion: 11 | |||||
mipmaps: | |||||
mipMapMode: 1 | |||||
enableMipMap: 1 | |||||
sRGBTexture: 1 | |||||
linearTexture: 0 | |||||
fadeOut: 0 | |||||
borderMipMap: 0 | |||||
mipMapsPreserveCoverage: 0 | |||||
alphaTestReferenceValue: 0.5 | |||||
mipMapFadeDistanceStart: 1 | |||||
mipMapFadeDistanceEnd: 3 | |||||
bumpmap: | |||||
convertToNormalMap: 0 | |||||
externalNormalMap: 0 | |||||
heightScale: 0.25 | |||||
normalMapFilter: 0 | |||||
isReadable: 0 | |||||
streamingMipmaps: 0 | |||||
streamingMipmapsPriority: 0 | |||||
grayScaleToAlpha: 0 | |||||
generateCubemap: 6 | |||||
cubemapConvolution: 0 | |||||
seamlessCubemap: 0 | |||||
textureFormat: 1 | |||||
maxTextureSize: 2048 | |||||
textureSettings: | |||||
serializedVersion: 2 | |||||
filterMode: -1 | |||||
aniso: 1 | |||||
mipBias: -100 | |||||
wrapU: 1 | |||||
wrapV: 1 | |||||
wrapW: -1 | |||||
nPOTScale: 0 | |||||
lightmap: 0 | |||||
compressionQuality: 50 | |||||
spriteMode: 1 | |||||
spriteExtrude: 1 | |||||
spriteMeshType: 1 | |||||
alignment: 0 | |||||
spritePivot: {x: 0.5, y: 0.5} | |||||
spritePixelsToUnits: 100 | |||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||||
spriteGenerateFallbackPhysicsShape: 1 | |||||
alphaUsage: 1 | |||||
alphaIsTransparency: 1 | |||||
spriteTessellationDetail: -1 | |||||
textureType: 2 | |||||
textureShape: 1 | |||||
singleChannelComponent: 0 | |||||
maxTextureSizeSet: 0 | |||||
compressionQualitySet: 0 | |||||
textureFormatSet: 0 | |||||
applyGammaDecoding: 0 | |||||
platformSettings: | |||||
- serializedVersion: 3 | |||||
buildTarget: DefaultTexturePlatform | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Standalone | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Android | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: WebGL | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
spriteSheet: | |||||
serializedVersion: 2 | |||||
sprites: [] | |||||
outline: [] | |||||
physicsShape: [] | |||||
bones: [] | |||||
spriteID: 21fab31b34964614680dcb53be2692d1 | |||||
internalID: 0 | |||||
vertices: [] | |||||
indices: | |||||
edges: [] | |||||
weights: [] | |||||
secondaryTextures: [] | |||||
spritePackingTag: | |||||
pSDRemoveMatte: 0 | |||||
pSDShowRemoveMatteOption: 0 | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,128 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 098c7906709c37c4bba842c34581765c | |||||
TextureImporter: | |||||
internalIDToNameTable: [] | |||||
externalObjects: {} | |||||
serializedVersion: 11 | |||||
mipmaps: | |||||
mipMapMode: 0 | |||||
enableMipMap: 1 | |||||
sRGBTexture: 1 | |||||
linearTexture: 0 | |||||
fadeOut: 0 | |||||
borderMipMap: 0 | |||||
mipMapsPreserveCoverage: 0 | |||||
alphaTestReferenceValue: 0.5 | |||||
mipMapFadeDistanceStart: 1 | |||||
mipMapFadeDistanceEnd: 3 | |||||
bumpmap: | |||||
convertToNormalMap: 0 | |||||
externalNormalMap: 0 | |||||
heightScale: 0.25 | |||||
normalMapFilter: 0 | |||||
isReadable: 0 | |||||
streamingMipmaps: 0 | |||||
streamingMipmapsPriority: 0 | |||||
grayScaleToAlpha: 0 | |||||
generateCubemap: 6 | |||||
cubemapConvolution: 0 | |||||
seamlessCubemap: 0 | |||||
textureFormat: 1 | |||||
maxTextureSize: 2048 | |||||
textureSettings: | |||||
serializedVersion: 2 | |||||
filterMode: -1 | |||||
aniso: 1 | |||||
mipBias: -100 | |||||
wrapU: 1 | |||||
wrapV: 1 | |||||
wrapW: -1 | |||||
nPOTScale: 0 | |||||
lightmap: 0 | |||||
compressionQuality: 50 | |||||
spriteMode: 1 | |||||
spriteExtrude: 1 | |||||
spriteMeshType: 1 | |||||
alignment: 0 | |||||
spritePivot: {x: 0.5, y: 0.5} | |||||
spritePixelsToUnits: 100 | |||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||||
spriteGenerateFallbackPhysicsShape: 1 | |||||
alphaUsage: 1 | |||||
alphaIsTransparency: 1 | |||||
spriteTessellationDetail: -1 | |||||
textureType: 2 | |||||
textureShape: 1 | |||||
singleChannelComponent: 0 | |||||
maxTextureSizeSet: 0 | |||||
compressionQualitySet: 0 | |||||
textureFormatSet: 0 | |||||
applyGammaDecoding: 0 | |||||
platformSettings: | |||||
- serializedVersion: 3 | |||||
buildTarget: DefaultTexturePlatform | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Standalone | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: Android | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
- serializedVersion: 3 | |||||
buildTarget: WebGL | |||||
maxTextureSize: 2048 | |||||
resizeAlgorithm: 0 | |||||
textureFormat: -1 | |||||
textureCompression: 1 | |||||
compressionQuality: 50 | |||||
crunchedCompression: 0 | |||||
allowsAlphaSplitting: 0 | |||||
overridden: 0 | |||||
androidETC2FallbackOverride: 0 | |||||
forceMaximumCompressionQuality_BC6H_BC7: 0 | |||||
spriteSheet: | |||||
serializedVersion: 2 | |||||
sprites: [] | |||||
outline: [] | |||||
physicsShape: [] | |||||
bones: [] | |||||
spriteID: 21fab31b34964614680dcb53be2692d1 | |||||
internalID: 0 | |||||
vertices: [] | |||||
indices: | |||||
edges: [] | |||||
weights: [] | |||||
secondaryTextures: [] | |||||
spritePackingTag: | |||||
pSDRemoveMatte: 0 | |||||
pSDShowRemoveMatteOption: 0 | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,104 @@ | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
using static UnityEditor.EditorGUILayout; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
[CanEditMultipleObjects] | |||||
[CustomEditor(typeof(TrueShadow))] | |||||
public class TrueShadowEditor : UnityEditor.Editor | |||||
{ | |||||
EditorProperty insetProp; | |||||
EditorProperty sizeProp; | |||||
EditorProperty spreadProp; | |||||
EditorProperty angleProp; | |||||
EditorProperty distanceProp; | |||||
EditorProperty colorProp; | |||||
EditorProperty blendModeProp; | |||||
EditorProperty multiplyCasterAlphaProp; | |||||
EditorProperty ignoreCasterColorProp; | |||||
EditorProperty colorBleedModeProp; | |||||
GUIContent procrastinateLabel; | |||||
static bool showExperimental; | |||||
static bool showAdvanced; | |||||
void OnEnable() | |||||
{ | |||||
insetProp = new EditorProperty(serializedObject, nameof(TrueShadow.Inset)); | |||||
sizeProp = new EditorProperty(serializedObject, nameof(TrueShadow.Size)); | |||||
spreadProp = new EditorProperty(serializedObject, nameof(TrueShadow.Spread)); | |||||
angleProp = new EditorProperty(serializedObject, nameof(TrueShadow.OffsetAngle)); | |||||
distanceProp = new EditorProperty(serializedObject, nameof(TrueShadow.OffsetDistance)); | |||||
colorProp = new EditorProperty(serializedObject, nameof(TrueShadow.Color)); | |||||
blendModeProp = new EditorProperty(serializedObject, nameof(TrueShadow.BlendMode)); | |||||
multiplyCasterAlphaProp = new EditorProperty(serializedObject, nameof(TrueShadow.UseCasterAlpha)); | |||||
ignoreCasterColorProp = new EditorProperty(serializedObject, nameof(TrueShadow.IgnoreCasterColor)); | |||||
colorBleedModeProp = new EditorProperty(serializedObject, nameof(TrueShadow.ColorBleedMode)); | |||||
if (EditorPrefs.GetBool("LeTai_TrueShadow_" + nameof(showExperimental))) | |||||
{ | |||||
showExperimental = EditorPrefs.GetBool("LeTai_TrueShadow_" + nameof(showExperimental), false); | |||||
showAdvanced = EditorPrefs.GetBool("LeTai_TrueShadow_" + nameof(showAdvanced), false); | |||||
} | |||||
procrastinateLabel = new GUIContent("Procrastinate", "A bug that is too fun to fix"); | |||||
} | |||||
public override void OnInspectorGUI() | |||||
{ | |||||
serializedObject.Update(); | |||||
var ts = (TrueShadow) target; | |||||
insetProp.Draw(); | |||||
sizeProp.Draw(); | |||||
spreadProp.Draw(); | |||||
angleProp.Draw(); | |||||
distanceProp.Draw(); | |||||
colorProp.Draw(); | |||||
if (ts.UsingRendererMaterialProvider) | |||||
{ | |||||
using(new EditorGUI.DisabledScope(true)) | |||||
LabelField(blendModeProp.serializedProperty.displayName, "Custom Material"); | |||||
} | |||||
else | |||||
{ | |||||
blendModeProp.Draw(); | |||||
} | |||||
using (var change = new EditorGUI.ChangeCheckScope()) | |||||
{ | |||||
showAdvanced = Foldout(showAdvanced, "Advanced Settings", true); | |||||
using (new EditorGUI.IndentLevelScope()) | |||||
if (showAdvanced) | |||||
{ | |||||
multiplyCasterAlphaProp.Draw(); | |||||
ignoreCasterColorProp.Draw(); | |||||
colorBleedModeProp.Draw(); | |||||
if (KnobPropertyDrawer.procrastinationMode) | |||||
{ | |||||
var rot = GUI.matrix; | |||||
GUI.matrix = Matrix4x4.identity; | |||||
KnobPropertyDrawer.procrastinationMode ^= Toggle("Be Productive", false); | |||||
GUI.matrix = rot; | |||||
} | |||||
else | |||||
{ | |||||
KnobPropertyDrawer.procrastinationMode |= Toggle(procrastinateLabel, false); | |||||
} | |||||
} | |||||
if (change.changed) | |||||
{ | |||||
EditorPrefs.SetBool("LeTai_TrueShadow_" + nameof(showExperimental), showExperimental); | |||||
EditorPrefs.SetBool("LeTai_TrueShadow_" + nameof(showAdvanced), showAdvanced); | |||||
} | |||||
} | |||||
serializedObject.ApplyModifiedProperties(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 120b61c7775149ed9de9c782978f6d61 | |||||
timeCreated: 1592985822 |
@ -0,0 +1,22 @@ | |||||
using UnityEditor; | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow.Editor | |||||
{ | |||||
public static class Utility | |||||
{ | |||||
public static T FindAsset<T>(string assetName) where T : UnityEngine.Object | |||||
{ | |||||
var guids = AssetDatabase.FindAssets("l:TrueShadowEditorResources " + assetName); | |||||
if (guids.Length == 0) | |||||
{ | |||||
Debug.LogError($"Asset \"{assetName}\" not found. " + | |||||
$"Make sure it have the label \"TrueShadowEditorResources\""); | |||||
return null; | |||||
} | |||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]); | |||||
return AssetDatabase.LoadAssetAtPath<T>(path); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 09b72999f3c04f898c16bf60deb38c6e | |||||
timeCreated: 1602578784 |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 20daae54a38548b488eb9c2aa1d39507 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,9 @@ | |||||
using UnityEngine; | |||||
namespace LeTai.Effects | |||||
{ | |||||
public class BlurConfig : ScriptableObject | |||||
{ | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 1598e613c3d69154db682bbf3c234cb2 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,15 @@ | |||||
using UnityEngine; | |||||
using UnityEngine.Rendering; | |||||
namespace LeTai.Effects | |||||
{ | |||||
public interface IBlurAlgorithm | |||||
{ | |||||
void Configure(BlurConfig config); | |||||
void Blur(CommandBuffer cmd, | |||||
RenderTargetIdentifier src, | |||||
Rect srcCropRegion, | |||||
RenderTexture target); | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 5bc98ab7243bbdc40a0c52f6d1ed58a0 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,104 @@ | |||||
using UnityEngine; | |||||
using UnityEngine.Rendering; | |||||
namespace LeTai.Effects | |||||
{ | |||||
public class ScalableBlur : IBlurAlgorithm | |||||
{ | |||||
Material material; | |||||
ScalableBlurConfig config; | |||||
const int BLUR_PASS = 0; | |||||
const int CROP_BLUR_PASS = 1; | |||||
Material Material | |||||
{ | |||||
get | |||||
{ | |||||
if (material == null) | |||||
Material = new Material(Shader.Find("Hidden/EfficientBlur")); | |||||
return material; | |||||
} | |||||
set => material = value; | |||||
} | |||||
~ScalableBlur() | |||||
{ | |||||
if (!material) return; | |||||
if (Application.isPlaying) | |||||
Object.Destroy(material); | |||||
else | |||||
Object.DestroyImmediate(material); | |||||
} | |||||
public void Configure(BlurConfig config) | |||||
{ | |||||
this.config = (ScalableBlurConfig) config; | |||||
} | |||||
public void Blur(CommandBuffer cmd, | |||||
RenderTargetIdentifier src, | |||||
Rect srcCropRegion, | |||||
RenderTexture target) | |||||
{ | |||||
float radius = config.Radius; | |||||
Material.SetFloat(ShaderProperties.blurRadius, radius); | |||||
Material.SetVector(ShaderProperties.blurTextureCropRegion, srcCropRegion.ToMinMaxVector()); | |||||
int firstDownsampleFactor = config.Iteration > 0 ? 1 : 0; | |||||
int stepCount = Mathf.Max(config.Iteration * 2 - 1, 1); | |||||
int firstIRT = ShaderProperties.intermediateRT[0]; | |||||
CreateTempRenderTextureFrom(cmd, firstIRT, target, firstDownsampleFactor); | |||||
// cmd.BlitFullscreenTriangle(src, firstIRT, Material, CROP_BLUR_PASS); | |||||
cmd.Blit(src, firstIRT, Material, CROP_BLUR_PASS); | |||||
for (var i = 1; i < stepCount; i++) | |||||
{ | |||||
BlurAtDepth(cmd, i, target); | |||||
} | |||||
// cmd.BlitFullscreenTriangle(ShaderProperties.intermediateRT[stepCount - 1], target, Material, BLUR_PASS); | |||||
cmd.Blit(ShaderProperties.intermediateRT[stepCount - 1], target, Material, BLUR_PASS); | |||||
CleanupIntermediateRT(cmd, stepCount); | |||||
} | |||||
protected virtual void BlurAtDepth(CommandBuffer cmd, int depth, RenderTexture baseTexture) | |||||
{ | |||||
int sizeLevel = Utility.SimplePingPong(depth, config.Iteration - 1) + 1; | |||||
sizeLevel = Mathf.Min(sizeLevel, config.MaxDepth); | |||||
CreateTempRenderTextureFrom(cmd, ShaderProperties.intermediateRT[depth], baseTexture, sizeLevel); | |||||
// cmd.BlitFullscreenTriangle( | |||||
cmd.Blit( | |||||
ShaderProperties.intermediateRT[depth - 1], | |||||
ShaderProperties.intermediateRT[depth], | |||||
Material, | |||||
0 | |||||
); | |||||
} | |||||
static void CreateTempRenderTextureFrom(CommandBuffer cmd, | |||||
int nameId, | |||||
RenderTexture src, | |||||
int downsampleFactor) | |||||
{ | |||||
int w = src.width >> downsampleFactor; //= width / 2^downsample | |||||
int h = src.height >> downsampleFactor; | |||||
cmd.GetTemporaryRT(nameId, w, h, 0, FilterMode.Bilinear, src.format); | |||||
} | |||||
static void CleanupIntermediateRT(CommandBuffer cmd, int amount) | |||||
{ | |||||
for (var i = 0; i < amount; i++) | |||||
{ | |||||
cmd.ReleaseTemporaryRT(ShaderProperties.intermediateRT[i]); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: d72465d1220a01e4abccda77619f6763 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,110 @@ | |||||
using UnityEngine; | |||||
using static UnityEngine.Mathf; | |||||
namespace LeTai.Effects | |||||
{ | |||||
public class ScalableBlurConfig : BlurConfig | |||||
{ | |||||
[SerializeField] float radius = 4; | |||||
[SerializeField] int iteration = 4; | |||||
[SerializeField] int maxDepth = 6; | |||||
[SerializeField] [Range(0, 256)] float strength; | |||||
/// <summary> | |||||
/// Distance between the base texel and the texel to be sampled. | |||||
/// </summary> | |||||
public float Radius | |||||
{ | |||||
get { return radius; } | |||||
set { radius = Max(0, value); } | |||||
} | |||||
/// <summary> | |||||
/// Half the number of time to process the image. It is half because the real number of iteration must alway be even. Using half also make calculation simpler | |||||
/// </summary> | |||||
/// <value> | |||||
/// Must be non-negative | |||||
/// </value> | |||||
public int Iteration | |||||
{ | |||||
get { return iteration; } | |||||
set { iteration = Max(0, value); } | |||||
} | |||||
/// <summary> | |||||
/// Clamp the minimum size of the intermediate texture. Reduce flickering and blur | |||||
/// </summary> | |||||
/// <value> | |||||
/// Must larger than 0 | |||||
/// </value> | |||||
public int MaxDepth | |||||
{ | |||||
get { return maxDepth; } | |||||
set { maxDepth = Max(1, value); } | |||||
} | |||||
/// <summary> | |||||
/// User friendly property to control the amount of blur | |||||
/// </summary> | |||||
///<value> | |||||
/// Must be non-negative | |||||
/// </value> | |||||
public float Strength | |||||
{ | |||||
get { return strength = radius * (3 * (1 << iteration) - 2) / UNIT_VARIANCE; } | |||||
set | |||||
{ | |||||
strength = Max(0, value); | |||||
SetAdvancedFieldFromSimple(); | |||||
} | |||||
} | |||||
// With the "correct" unit variance, the edge of the shadow at higher stddev go below 8bit fixed point resolution | |||||
// We "wastes" processing power on these. | |||||
// TODO: optimize that: | |||||
// The maximum distance that will show up is: | |||||
// e^(-D^2 / 2R^2) < .5/256 | |||||
// => D < 3*sqrt(2*log(2)) * R ~ 3.53223*R | |||||
// Can probably stop sooner than that | |||||
static readonly float UNIT_VARIANCE = 1f + Sqrt(2f) / 2f; | |||||
/// <summary> | |||||
/// Calculate size and iteration from strength | |||||
/// </summary> | |||||
protected virtual void SetAdvancedFieldFromSimple() | |||||
{ | |||||
if (strength < 1e-2) | |||||
{ | |||||
iteration = 0; | |||||
radius = 0; | |||||
return; | |||||
} | |||||
var variance = strength * UNIT_VARIANCE; | |||||
// Each level of the pyramid have double the effective radius of the last, so total effective radius would be: | |||||
// S = (2^0 + 2^1 +...+ 2^iteration +...+ 2^1 + 2^0) * R | |||||
// https://en.wikipedia.org/wiki/1_%2B_2_%2B_4_%2B_8_%2B_%E2%8B%AF | |||||
// S = (3 * 2^I - 2) * R | |||||
// so: | |||||
// I = log((s + 2r)/ (3r))/log(2) | |||||
// and: | |||||
// R = S / (3 * 2^I - 2) | |||||
// | |||||
// Experimental result show that best result are obtained with R <= 2^I - 1, so: | |||||
// I >= log(1/6 * (sqrt(12S + 1) + 5))/log(2) | |||||
// | |||||
// There still some artifact at the lower end, not sure how to handle that yet | |||||
// TODO: use a different algorithm for low Strength. | |||||
iteration = CeilToInt(Log(1 / 6f * (Sqrt(12 * variance + 1) + 5)) / Log(2)); | |||||
radius = variance / (3 * (1 << iteration) - 2); | |||||
} | |||||
void OnValidate() | |||||
{ | |||||
SetAdvancedFieldFromSimple(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 506e4e7bc6f8ce24486de8c2732c0a1b | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,37 @@ | |||||
using UnityEngine; | |||||
namespace LeTai.Effects | |||||
{ | |||||
public static class ShaderProperties | |||||
{ | |||||
private static bool isInitialized; | |||||
public static int[] intermediateRT; | |||||
public static int blurRadius; | |||||
public static int blurTextureCropRegion; | |||||
public static void Init() | |||||
{ | |||||
if (isInitialized) | |||||
return; | |||||
blurRadius = Shader.PropertyToID("_Radius"); | |||||
blurTextureCropRegion = Shader.PropertyToID("_CropRegion"); | |||||
isInitialized = true; | |||||
} | |||||
public static void Init(int stackDepth) | |||||
{ | |||||
intermediateRT = new int[stackDepth * 2 - 1]; | |||||
for (var i = 0; i < intermediateRT.Length; i++) | |||||
{ | |||||
intermediateRT[i] = Shader.PropertyToID(string.Format("TI_intermediate_rt_{0}", i)); | |||||
} | |||||
Init(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 3ae61d4a8774bbb41acdf25cded306c2 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: fa263cbfa5524d12a0df2e7befd04f50 | |||||
timeCreated: 1593508707 |
@ -0,0 +1,138 @@ | |||||
using System; | |||||
using UnityEngine; | |||||
using UnityEngine.EventSystems; | |||||
public class AnimatedBiStateButton : MonoBehaviour, | |||||
IPointerDownHandler, IPointerUpHandler, | |||||
IPointerEnterHandler, IPointerExitHandler | |||||
{ | |||||
public enum State | |||||
{ | |||||
Up, | |||||
AnimateDown, | |||||
Down, | |||||
AnimateUp, | |||||
} | |||||
public float animationDuration = .1f; | |||||
public AnimationCurve animationCurve = AnimationCurve.EaseInOut(0, 0, 1, 1); | |||||
public bool useEnterExitEvents = true; | |||||
public event Action willPress; | |||||
public event Action willRelease; | |||||
protected State state = State.Up; | |||||
/// <summary> | |||||
/// 0 = fully up | |||||
/// 1 = fully down | |||||
/// </summary> | |||||
protected float pressAmount = 0; | |||||
protected bool IsAnimating => state == State.AnimateDown || state == State.AnimateUp; | |||||
void Update() | |||||
{ | |||||
PollPointerUp(); | |||||
DoAnimation(); | |||||
} | |||||
void DoAnimation() | |||||
{ | |||||
if (!IsAnimating) return; | |||||
if (state == State.AnimateDown) | |||||
{ | |||||
pressAmount += Time.deltaTime / animationDuration; | |||||
} | |||||
else if (state == State.AnimateUp) | |||||
{ | |||||
pressAmount -= Time.deltaTime / animationDuration; | |||||
} | |||||
pressAmount = Mathf.Clamp01(pressAmount); | |||||
var animationProgress = pressAmount; | |||||
if (state == State.AnimateUp) animationProgress = 1 - animationProgress; | |||||
animationProgress = animationCurve.Evaluate(animationProgress); | |||||
if (state == State.AnimateUp) animationProgress = 1 - animationProgress; | |||||
Animate(animationProgress); | |||||
if (state == State.AnimateDown && pressAmount == 1) | |||||
{ | |||||
state = State.Down; | |||||
} | |||||
if (state == State.AnimateUp && pressAmount == 0) | |||||
{ | |||||
state = State.Up; | |||||
} | |||||
} | |||||
protected void Press() | |||||
{ | |||||
if (state != State.Down && state != State.AnimateDown) | |||||
{ | |||||
OnWillPress(); | |||||
state = State.AnimateDown; | |||||
} | |||||
} | |||||
protected void Release() | |||||
{ | |||||
if (state != State.Up && state != State.AnimateUp) | |||||
{ | |||||
OnWillRelease(); | |||||
state = State.AnimateUp; | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// Pointer Up event does not fire on an object if it was not the one receive the Pointer Down event. | |||||
/// </summary> | |||||
void PollPointerUp() | |||||
{ | |||||
if (useEnterExitEvents | |||||
&& (state == State.Down || state == State.AnimateDown) | |||||
&& !Input.GetMouseButton(0)) | |||||
{ | |||||
Release(); | |||||
} | |||||
} | |||||
/// <summary> | |||||
/// NOP if not overrided | |||||
/// </summary> | |||||
/// <param name="visualPressAmount"><see cref="pressAmount"/> conformed to <see cref="animationCurve"/></param> | |||||
protected virtual void Animate(float visualPressAmount) { } | |||||
public void OnPointerDown(PointerEventData eventData) | |||||
{ | |||||
Press(); | |||||
} | |||||
public void OnPointerUp(PointerEventData eventData) | |||||
{ | |||||
Release(); | |||||
} | |||||
public void OnPointerEnter(PointerEventData eventData) | |||||
{ | |||||
if (useEnterExitEvents && Input.GetMouseButton(0)) Press(); | |||||
} | |||||
public void OnPointerExit(PointerEventData eventData) | |||||
{ | |||||
if (useEnterExitEvents) Release(); | |||||
} | |||||
protected virtual void OnWillPress() | |||||
{ | |||||
willPress?.Invoke(); | |||||
} | |||||
protected virtual void OnWillRelease() | |||||
{ | |||||
willRelease?.Invoke(); | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: d498001a5177468b90ceba93a17a5307 | |||||
timeCreated: 1603966347 |
@ -0,0 +1,69 @@ | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
[RequireComponent(typeof(TrueShadow))] | |||||
public class InsetOnPress : AnimatedBiStateButton | |||||
{ | |||||
TrueShadow[] shadows; | |||||
float[] normalOpacity; | |||||
bool wasInset; | |||||
void OnEnable() | |||||
{ | |||||
shadows = GetComponents<TrueShadow>(); | |||||
normalOpacity = new float[shadows.Length]; | |||||
} | |||||
protected override void Animate(float visualPressAmount) | |||||
{ | |||||
void SetAllOpacity(float lerpProgress) | |||||
{ | |||||
for (var i = 0; i < shadows.Length; i++) | |||||
{ | |||||
var color = shadows[i].Color; | |||||
color.a = Mathf.Lerp(0, normalOpacity[i], lerpProgress); | |||||
shadows[i].Color = color; | |||||
} | |||||
} | |||||
bool shouldInset = visualPressAmount > .5f; | |||||
if (shouldInset != wasInset) | |||||
{ | |||||
for (var i = 0; i < shadows.Length; i++) | |||||
{ | |||||
shadows[i].Inset = shouldInset; | |||||
} | |||||
wasInset = shouldInset; | |||||
} | |||||
if (shouldInset) | |||||
{ | |||||
SetAllOpacity(visualPressAmount * 2f - 1f); | |||||
} | |||||
else | |||||
{ | |||||
SetAllOpacity(1 - visualPressAmount * 2f); | |||||
} | |||||
} | |||||
void MemorizeOpacity() | |||||
{ | |||||
if (IsAnimating) return; | |||||
for (var i = 0; i < shadows.Length; i++) | |||||
{ | |||||
normalOpacity[i] = shadows[i].Color.a; | |||||
} | |||||
} | |||||
protected override void OnWillPress() | |||||
{ | |||||
wasInset = shadows[0].Inset; | |||||
MemorizeOpacity(); | |||||
base.OnWillPress(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: dd53a06400e8496e82467031771bf46e | |||||
timeCreated: 1602747228 |
@ -0,0 +1,242 @@ | |||||
using System.Collections; | |||||
using UnityEngine; | |||||
using UnityEngine.EventSystems; | |||||
using UnityEngine.UI; | |||||
using static UnityEngine.Mathf; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
[RequireComponent(typeof(TrueShadow))] | |||||
public class InteractiveShadow : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, | |||||
IDeselectHandler, IPointerDownHandler, IPointerUpHandler | |||||
{ | |||||
public float smoothTime = .05f; | |||||
[Tooltip("Deselect on pointer up")] | |||||
public bool autoDeselect; | |||||
[Header("Size")] | |||||
public float selectedSize = 28; | |||||
public float hoverSize = 28; | |||||
public float clickedSize = 24; | |||||
[Header("Distance")] | |||||
public float selectedDistance = 12; | |||||
public float hoverDistance = 12; | |||||
public float clickedDistance = 8; | |||||
[Header("Color")] | |||||
public Color selectedColor = new Color(0, 0, 0, .25f); | |||||
public Color hoverColor = new Color(0, 0, 0, .20f); | |||||
public Color clickedColor = new Color(0, 0, 0, .25f); | |||||
float normalSize; | |||||
float normalDistance; | |||||
Color normalColor; | |||||
bool isSelected; | |||||
bool isHovered; | |||||
bool isClicked; | |||||
TrueShadow shadow; | |||||
Selectable selectable; | |||||
float targetSize; | |||||
float targetDistance; | |||||
Color targetColor; | |||||
static readonly Color FADED_COLOR = new Color(.5f, .5f, .5f, .5f); | |||||
#if UNITY_EDITOR | |||||
void Reset() | |||||
{ | |||||
shadow = FindTrueShadow(); | |||||
if (shadow) | |||||
{ | |||||
normalSize = shadow.Size; | |||||
normalDistance = shadow.OffsetDistance; | |||||
normalColor = shadow.Color; | |||||
// Clicked UI remain selected, which is unwanted. Selected state is probably most useful on console | |||||
// and keyboard nav, the later is rather hard to detect | |||||
bool selectedIsNormal = Input.mousePresent || Input.touchSupported; | |||||
autoDeselect = selectedIsNormal; | |||||
hoverSize = Round(Min(normalSize * 1.75f, normalSize + 20f)); | |||||
selectedSize = selectedIsNormal ? normalSize : hoverSize; | |||||
clickedSize = Round(Min(normalSize * 1.25f, normalSize + 15f)); | |||||
hoverDistance = Round(Min(normalDistance * 1.5f, normalDistance + 20f)); | |||||
selectedDistance = selectedIsNormal ? normalDistance : hoverDistance; | |||||
clickedDistance = Round(Min(normalDistance * 1.25f, normalDistance + 15f)); | |||||
hoverColor = Color.Lerp(normalColor, FADED_COLOR, .15f); | |||||
selectedColor = selectedIsNormal ? normalColor : hoverColor; | |||||
clickedColor = Color.Lerp(normalColor, FADED_COLOR, .25f); | |||||
} | |||||
} | |||||
#endif | |||||
void OnEnable() | |||||
{ | |||||
shadow = FindTrueShadow(); | |||||
selectable = GetComponent<Selectable>(); | |||||
targetSize = normalSize = shadow.Size; | |||||
targetDistance = normalDistance = shadow.OffsetDistance; | |||||
targetColor = normalColor = shadow.Color; | |||||
shadow.Size = targetSize = normalSize; | |||||
shadow.OffsetDistance = targetDistance = normalDistance; | |||||
} | |||||
TrueShadow FindTrueShadow() | |||||
{ | |||||
var shadows = GetComponents<TrueShadow>(); | |||||
if (shadows.Length == 0) return null; | |||||
var ishadows = GetComponents<InteractiveShadow>(); | |||||
int index = 0; | |||||
for (; index < ishadows.Length; index++) | |||||
if (ishadows[index] == this) | |||||
break; | |||||
return shadows[index]; | |||||
} | |||||
void OnStateChange() | |||||
{ | |||||
if (isClicked) | |||||
{ | |||||
targetSize = clickedSize; | |||||
targetDistance = clickedDistance; | |||||
targetColor = clickedColor; | |||||
} | |||||
else if (isSelected) | |||||
{ | |||||
targetSize = selectedSize; | |||||
targetDistance = selectedDistance; | |||||
targetColor = selectedColor; | |||||
} | |||||
else if (isHovered) | |||||
{ | |||||
targetSize = hoverSize; | |||||
targetDistance = hoverDistance; | |||||
targetColor = hoverColor; | |||||
} | |||||
else | |||||
{ | |||||
targetSize = normalSize; | |||||
targetDistance = normalDistance; | |||||
targetColor = normalColor; | |||||
} | |||||
if (selectable && selectable.interactable && selectable.isActiveAndEnabled) | |||||
{ | |||||
if (selectable.transition == Selectable.Transition.ColorTint) | |||||
{ | |||||
StopAllCoroutines(); | |||||
StartCoroutine(DirtyForSeconds(selectable.colors.fadeDuration)); | |||||
} | |||||
} | |||||
} | |||||
IEnumerator DirtyForSeconds(float duration) | |||||
{ | |||||
var start = Time.time; | |||||
while (start + duration >= Time.time) | |||||
{ | |||||
shadow.SetTextureDirty(); | |||||
yield return null; | |||||
} | |||||
} | |||||
#region AnimationState | |||||
float currentSizeVelocity; | |||||
float currentDistanceVelocity; | |||||
float currentColorRVelocity; | |||||
float currentColorGVelocity; | |||||
float currentColorBVelocity; | |||||
float currentColorAVelocity; | |||||
#endregion | |||||
void Update() | |||||
{ | |||||
if (!Approximately(targetSize, shadow.Size)) | |||||
{ | |||||
shadow.Size = SmoothDamp(shadow.Size, targetSize, | |||||
ref currentSizeVelocity, smoothTime); | |||||
} | |||||
if (!Approximately(targetDistance, shadow.OffsetDistance)) | |||||
{ | |||||
shadow.OffsetDistance = SmoothDamp(shadow.OffsetDistance, targetDistance, | |||||
ref currentDistanceVelocity, smoothTime); | |||||
} | |||||
var shadowColor = shadow.Color; | |||||
if (!Approximately(targetColor.a, shadowColor.a)) | |||||
{ | |||||
var r = SmoothDamp(shadowColor.r, targetColor.r, | |||||
ref currentColorRVelocity, smoothTime); | |||||
var g = SmoothDamp(shadowColor.g, targetColor.g, | |||||
ref currentColorGVelocity, smoothTime); | |||||
var b = SmoothDamp(shadowColor.b, targetColor.b, | |||||
ref currentColorBVelocity, smoothTime); | |||||
var a = SmoothDamp(shadowColor.a, targetColor.a, | |||||
ref currentColorAVelocity, smoothTime); | |||||
shadow.Color = new Color(r, g, b, a); | |||||
} | |||||
} | |||||
public void OnPointerEnter(PointerEventData eventData) | |||||
{ | |||||
isHovered = true; | |||||
OnStateChange(); | |||||
} | |||||
public void OnPointerExit(PointerEventData eventData) | |||||
{ | |||||
isHovered = false; | |||||
OnStateChange(); | |||||
} | |||||
public void OnSelect(BaseEventData eventData) | |||||
{ | |||||
isSelected = true; | |||||
OnStateChange(); | |||||
} | |||||
public void OnDeselect(BaseEventData eventData) | |||||
{ | |||||
isSelected = false; | |||||
OnStateChange(); | |||||
} | |||||
public void OnPointerDown(PointerEventData eventData) | |||||
{ | |||||
isClicked = true; | |||||
OnStateChange(); | |||||
} | |||||
public void OnPointerUp(PointerEventData eventData) | |||||
{ | |||||
if (autoDeselect && EventSystem.current.currentSelectedGameObject == gameObject) | |||||
EventSystem.current.SetSelectedGameObject(null); | |||||
isClicked = false; | |||||
OnStateChange(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 2b6bd66378f54ffc974312b194733dd9 | |||||
timeCreated: 1593508708 |
@ -0,0 +1,40 @@ | |||||
using UnityEngine; | |||||
using UnityEngine.UI; | |||||
namespace LeTai.TrueShadow.PluginInterfaces | |||||
{ | |||||
public interface ITrueShadowCasterMaterialProvider | |||||
{ | |||||
Material GetTrueShadowCasterMaterial(); | |||||
} | |||||
public interface ITrueShadowCasterMeshModifier | |||||
{ | |||||
void ModifyTrueShadowCasterMesh(Mesh mesh); | |||||
} | |||||
public interface ITrueShadowCasterMaterialPropertiesModifier | |||||
{ | |||||
void ModifyTrueShadowCasterMaterialProperties(MaterialPropertyBlock propertyBlock); | |||||
} | |||||
public interface ITrueShadowCasterClearColorProvider | |||||
{ | |||||
Color GetTrueShadowCasterClearColor(); | |||||
} | |||||
public interface ITrueShadowRendererMaterialProvider | |||||
{ | |||||
Material GetTrueShadowRendererMaterial(); | |||||
} | |||||
public interface ITrueShadowRendererMaterialModifier | |||||
{ | |||||
void ModifyTrueShadowRendererMaterial(Material baseMaterial); | |||||
} | |||||
public interface ITrueShadowRendererMeshModifier | |||||
{ | |||||
void ModifyTrueShadowRenderMesh(VertexHelper vertexHelper); | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 81f076a2e89b8b34da77ca09f22cfe59 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,312 @@ | |||||
using System.Collections.Generic; | |||||
using LeTai.Effects; | |||||
using UnityEngine; | |||||
using UnityEngine.Rendering; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
public class ShadowFactory | |||||
{ | |||||
private static ShadowFactory instance; | |||||
public static ShadowFactory Instance => instance ?? (instance = new ShadowFactory()); | |||||
readonly Dictionary<int, ShadowContainer> shadowCache = | |||||
new Dictionary<int, ShadowContainer>(); | |||||
readonly CommandBuffer cmd; | |||||
readonly MaterialPropertyBlock materialProps; | |||||
readonly ScalableBlur blurProcessor; | |||||
readonly ScalableBlurConfig blurConfig; | |||||
Material cutoutMaterial; | |||||
Material imprintPostProcessMaterial; | |||||
Material shadowPostProcessMaterial; | |||||
Material CutoutMaterial => | |||||
cutoutMaterial ? cutoutMaterial : cutoutMaterial = new Material(Shader.Find("Hidden/TrueShadow/Cutout")); | |||||
Material ImprintPostProcessMaterial => | |||||
imprintPostProcessMaterial | |||||
? imprintPostProcessMaterial | |||||
: imprintPostProcessMaterial = new Material(Shader.Find("Hidden/TrueShadow/ImprintPostProcess")); | |||||
Material ShadowPostProcessMaterial => | |||||
shadowPostProcessMaterial | |||||
? shadowPostProcessMaterial | |||||
: shadowPostProcessMaterial = new Material(Shader.Find("Hidden/TrueShadow/PostProcess")); | |||||
private ShadowFactory() | |||||
{ | |||||
cmd = new CommandBuffer {name = "Shadow Commands"}; | |||||
materialProps = new MaterialPropertyBlock(); | |||||
materialProps.SetVector(ShaderId.CLIP_RECT, | |||||
new Vector4(float.NegativeInfinity, float.NegativeInfinity, | |||||
float.PositiveInfinity, float.PositiveInfinity)); | |||||
materialProps.SetInt(ShaderId.COLOR_MASK, (int) ColorWriteMask.All); // Render shadow even if mask hide graphic | |||||
ShaderProperties.Init(8); | |||||
blurConfig = ScriptableObject.CreateInstance<ScalableBlurConfig>(); | |||||
blurConfig.hideFlags = HideFlags.HideAndDontSave; | |||||
blurProcessor = new ScalableBlur(); | |||||
blurProcessor.Configure(blurConfig); | |||||
} | |||||
~ShadowFactory() | |||||
{ | |||||
cmd.Dispose(); | |||||
Utility.SafeDestroy(blurConfig); | |||||
Utility.SafeDestroy(cutoutMaterial); | |||||
Utility.SafeDestroy(imprintPostProcessMaterial); | |||||
} | |||||
#if LETAI_TRUESHADOW_DEBUG | |||||
RenderTexture debugTexture; | |||||
#endif | |||||
// public int createdContainerCount; | |||||
// public int releasedContainerCount; | |||||
internal void Get(ShadowSettingSnapshot snapshot, ref ShadowContainer container) | |||||
{ | |||||
if (float.IsNaN(snapshot.dimensions.x) || snapshot.dimensions.x < 1 || | |||||
float.IsNaN(snapshot.dimensions.y) || snapshot.dimensions.y < 1) | |||||
{ | |||||
ReleaseContainer(container); | |||||
return; | |||||
} | |||||
#if LETAI_TRUESHADOW_DEBUG | |||||
RenderTexture.ReleaseTemporary(debugTexture); | |||||
if (snapshot.shadow.alwaysRender) | |||||
debugTexture = GenerateShadow(snapshot).Texture; | |||||
#endif | |||||
// Each request need a coresponding shadow texture | |||||
// Texture may be shared by multiple elements | |||||
// Texture are released when no longer used by any element | |||||
// ShadowContainer keep track of texture and their usage | |||||
int requestHash = snapshot.GetHashCode(); | |||||
// Case: requester can keep the same texture | |||||
if (container?.requestHash == requestHash) | |||||
return; | |||||
ReleaseContainer(container); | |||||
if (shadowCache.TryGetValue(requestHash, out var existingContainer)) | |||||
{ | |||||
// Case: requester got texture from someone else | |||||
existingContainer.RefCount++; | |||||
container = existingContainer; | |||||
} | |||||
else | |||||
{ | |||||
// Case: requester got new unique texture | |||||
container = shadowCache[requestHash] = GenerateShadow(snapshot); | |||||
// Debug.Log($"Created new container for request\t{requestHash}\tTotal Created: {++createdContainerCount}\t Alive: {createdContainerCount - releasedContainerCount}"); | |||||
} | |||||
} | |||||
internal void ReleaseContainer(ShadowContainer container) | |||||
{ | |||||
if (container == null) | |||||
return; | |||||
if (--container.RefCount > 0) | |||||
return; | |||||
RenderTexture.ReleaseTemporary(container.Texture); | |||||
shadowCache.Remove(container.requestHash); | |||||
// Debug.Log($"Released container for request\t{container.requestHash}\tTotal Released: {++releasedContainerCount}\t Alive: {createdContainerCount - releasedContainerCount}"); | |||||
} | |||||
static readonly Rect UNIT_RECT = new Rect(0, 0, 1, 1); | |||||
ShadowContainer GenerateShadow(ShadowSettingSnapshot snapshot) | |||||
{ | |||||
// return GenColoredTexture(request.GetHashCode()); | |||||
cmd.Clear(); | |||||
cmd.BeginSample("TrueShadow:Capture"); | |||||
var bounds = snapshot.shadow.SpriteMesh.bounds; | |||||
var misalignment = CalcMisalignment(snapshot.canvas, snapshot.canvasRt, snapshot.shadow.RectTransform, bounds); | |||||
var padding = Mathf.CeilToInt(snapshot.size); | |||||
var imprintViewW = Mathf.RoundToInt(snapshot.dimensions.x + misalignment.bothSS.x); | |||||
var imprintViewH = Mathf.RoundToInt(snapshot.dimensions.y + misalignment.bothSS.y); | |||||
var tw = imprintViewW + padding * 2; | |||||
var th = imprintViewH + padding * 2; | |||||
var shadowTex = RenderTexture.GetTemporary(tw, th, 0, RenderTextureFormat.ARGB32); | |||||
var imprintTexDesc = shadowTex.descriptor; | |||||
imprintTexDesc.msaaSamples = snapshot.shouldAntialiasImprint ? Mathf.Max(1, QualitySettings.antiAliasing) : 1; | |||||
var imprintTex = RenderTexture.GetTemporary(imprintTexDesc); | |||||
RenderTexture imprintTexProcessed = null; | |||||
bool needProcessImprint = snapshot.shadow.IgnoreCasterColor || snapshot.shadow.Inset; | |||||
if (needProcessImprint) | |||||
imprintTexProcessed = RenderTexture.GetTemporary(imprintTexDesc); | |||||
var texture = snapshot.shadow.Content; | |||||
if (texture) | |||||
materialProps.SetTexture(ShaderId.MAIN_TEX, texture); | |||||
else | |||||
materialProps.SetTexture(ShaderId.MAIN_TEX, Texture2D.whiteTexture); | |||||
cmd.SetRenderTarget(imprintTex); | |||||
cmd.ClearRenderTarget(true, true, snapshot.shadow.ClearColor); | |||||
cmd.SetViewport(new Rect(padding, padding, imprintViewW, imprintViewH)); | |||||
var imprintBoundMin = (Vector2) bounds.min - misalignment.minLS; | |||||
var imprintBoundMax = (Vector2) bounds.max + misalignment.maxLS; | |||||
cmd.SetViewProjectionMatrices( | |||||
Matrix4x4.identity, | |||||
Matrix4x4.Ortho(imprintBoundMin.x, imprintBoundMax.x, | |||||
imprintBoundMin.y, imprintBoundMax.y, | |||||
-1, 1) | |||||
); | |||||
snapshot.shadow.ModifyShadowCastingMesh(snapshot.shadow.SpriteMesh); | |||||
snapshot.shadow.ModifyShadowCastingMaterialProperties(materialProps); | |||||
cmd.DrawMesh(snapshot.shadow.SpriteMesh, | |||||
Matrix4x4.identity, | |||||
snapshot.shadow.GetShadowCastingMaterial(), | |||||
0, 0, | |||||
materialProps); | |||||
if (needProcessImprint) | |||||
{ | |||||
ImprintPostProcessMaterial.SetKeyword("BLEACH", snapshot.shadow.IgnoreCasterColor); | |||||
ImprintPostProcessMaterial.SetKeyword("INSET", snapshot.shadow.Inset); | |||||
cmd.Blit(imprintTex, imprintTexProcessed, ImprintPostProcessMaterial); | |||||
} | |||||
cmd.EndSample("TrueShadow:Capture"); | |||||
var needPostProcess = snapshot.shadow.Spread > 1e-3; | |||||
cmd.BeginSample("TrueShadow:Cast"); | |||||
RenderTexture blurSrc = needProcessImprint ? imprintTexProcessed : imprintTex; | |||||
RenderTexture blurDst; | |||||
if (needPostProcess) | |||||
blurDst = RenderTexture.GetTemporary(shadowTex.descriptor); | |||||
else | |||||
blurDst = shadowTex; | |||||
if (snapshot.size < 1e-2) | |||||
{ | |||||
cmd.Blit(blurSrc, blurDst); | |||||
} | |||||
else | |||||
{ | |||||
blurConfig.Strength = snapshot.size; | |||||
blurProcessor.Blur(cmd, blurSrc, UNIT_RECT, blurDst); | |||||
} | |||||
cmd.EndSample("TrueShadow:Cast"); | |||||
var relativeOffset = new Vector2(snapshot.canvasRelativeOffset.x / tw, | |||||
snapshot.canvasRelativeOffset.y / th); | |||||
var overflowAlpha = snapshot.shadow.Inset ? 1 : 0; | |||||
if (needPostProcess) | |||||
{ | |||||
cmd.BeginSample("TrueShadow:PostProcess"); | |||||
ShadowPostProcessMaterial.SetTexture(ShaderId.SHADOW_TEX, blurDst); | |||||
ShadowPostProcessMaterial.SetVector(ShaderId.OFFSET, relativeOffset); | |||||
ShadowPostProcessMaterial.SetFloat(ShaderId.OVERFLOW_ALPHA, overflowAlpha); | |||||
ShadowPostProcessMaterial.SetFloat(ShaderId.ALPHA_MULTIPLIER, | |||||
1f / Mathf.Max(1e-6f, 1f - snapshot.shadow.Spread)); | |||||
cmd.SetViewport(UNIT_RECT); | |||||
cmd.Blit(blurSrc, shadowTex, ShadowPostProcessMaterial); | |||||
cmd.EndSample("TrueShadow:PostProcess"); | |||||
} | |||||
else if (snapshot.shadow.Cutout) | |||||
{ | |||||
cmd.BeginSample("TrueShadow:Cutout"); | |||||
CutoutMaterial.SetVector(ShaderId.OFFSET, relativeOffset); | |||||
CutoutMaterial.SetFloat(ShaderId.OVERFLOW_ALPHA, overflowAlpha); | |||||
cmd.SetViewport(UNIT_RECT); | |||||
cmd.Blit(blurSrc, shadowTex, CutoutMaterial); | |||||
cmd.EndSample("TrueShadow:Cutout"); | |||||
} | |||||
Graphics.ExecuteCommandBuffer(cmd); | |||||
RenderTexture.ReleaseTemporary(imprintTex); | |||||
RenderTexture.ReleaseTemporary(blurSrc); | |||||
if (needPostProcess) | |||||
RenderTexture.ReleaseTemporary(blurDst); | |||||
return new ShadowContainer(shadowTex, snapshot, padding, misalignment.minLS); | |||||
} | |||||
readonly struct PixelMisalignment | |||||
{ | |||||
public readonly Vector2 bothSS; | |||||
public readonly Vector2 minLS; | |||||
public readonly Vector2 maxLS; | |||||
public PixelMisalignment(Vector2 bothSS, Vector2 minLS, Vector2 maxLS) | |||||
{ | |||||
this.bothSS = bothSS; | |||||
this.minLS = minLS; | |||||
this.maxLS = maxLS; | |||||
} | |||||
} | |||||
PixelMisalignment CalcMisalignment(Canvas canvas, RectTransform canvasRt, RectTransform casterRt, Bounds meshBound) | |||||
{ | |||||
PixelMisalignment misalignment; | |||||
if (canvas.renderMode == RenderMode.WorldSpace) | |||||
{ | |||||
misalignment = new PixelMisalignment(); | |||||
} | |||||
else | |||||
{ | |||||
var referenceCamera = canvas.renderMode == RenderMode.ScreenSpaceCamera ? canvas.worldCamera : null; | |||||
var pxMisalignmentAtMin = casterRt.LocalToScreenPoint(meshBound.min, referenceCamera).Frac(); | |||||
var pxMisalignmentAtMax = | |||||
Vector2.one - casterRt.LocalToScreenPoint(meshBound.max, referenceCamera).Frac(); | |||||
if (pxMisalignmentAtMax.x > 1 - 1e-5) | |||||
pxMisalignmentAtMax.x = 0; | |||||
if (pxMisalignmentAtMax.y > 1 - 1e-5) | |||||
pxMisalignmentAtMax.y = 0; | |||||
misalignment = new PixelMisalignment( | |||||
pxMisalignmentAtMin + pxMisalignmentAtMax, | |||||
canvasRt.ScreenToCanvasSize(pxMisalignmentAtMin, referenceCamera), | |||||
canvasRt.ScreenToCanvasSize(pxMisalignmentAtMax, referenceCamera) | |||||
); | |||||
} | |||||
return misalignment; | |||||
} | |||||
RenderTexture GenColoredTexture(int hash) | |||||
{ | |||||
var tex = new Texture2D(1, 1); | |||||
tex.SetPixels32(new[] {new Color32((byte) (hash >> 8), (byte) (hash >> 16), (byte) (hash >> 24), 255)}); | |||||
tex.Apply(); | |||||
var rt = RenderTexture.GetTemporary(1, 1); | |||||
Graphics.Blit(tex, rt); | |||||
return rt; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: b8d1278f371c48a784a5294591372531 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,76 @@ | |||||
using System.Collections.Generic; | |||||
using UnityEngine; | |||||
using UnityEngine.Rendering; | |||||
using UnityEngine.UI; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
public partial class ShadowRenderer | |||||
{ | |||||
// TODO: cleanup unused mask materials | |||||
static readonly Dictionary<(bool, Material), Material> MASK_MATERIALS_CACHE = | |||||
new Dictionary<(bool, Material), Material>(); | |||||
internal static void ClearMaskMaterialCache() | |||||
{ | |||||
foreach (var keyValuePair in MASK_MATERIALS_CACHE) | |||||
{ | |||||
if(Application.isPlaying) | |||||
Destroy(keyValuePair.Value); | |||||
else | |||||
DestroyImmediate(keyValuePair.Value); | |||||
} | |||||
MASK_MATERIALS_CACHE.Clear(); | |||||
} | |||||
public Material GetModifiedMaterial(Material baseMaterial) | |||||
{ | |||||
if (!shadow) | |||||
return baseMaterial; | |||||
shadow.ModifyShadowRendererMaterial(baseMaterial); | |||||
if (!baseMaterial.HasProperty(ShaderId.COLOR_MASK) || | |||||
!baseMaterial.HasProperty(ShaderId.STENCIL_OP)) | |||||
return baseMaterial; | |||||
bool casterIsMask = shadow.GetComponent<Mask>() != null; | |||||
MASK_MATERIALS_CACHE.TryGetValue((casterIsMask, baseMaterial), out var mat); | |||||
if (!mat) | |||||
{ | |||||
mat = new Material(baseMaterial); | |||||
if (shadow.ShadowAsSibling) | |||||
{ | |||||
// Prevent shadow from writing to stencil mask | |||||
mat.SetInt(ShaderId.COLOR_MASK, (int) ColorWriteMask.All); | |||||
mat.SetInt(ShaderId.STENCIL_OP, (int) StencilOp.Keep); | |||||
} | |||||
else if (casterIsMask) | |||||
{ | |||||
// Escape mask if we have one | |||||
var baseStencilId = mat.GetInt(ShaderId.STENCIL_ID) + 1; | |||||
int stencilDepth = 0; | |||||
for (; stencilDepth < 8; stencilDepth++) | |||||
{ | |||||
if (((baseStencilId >> stencilDepth) & 1) == 1) | |||||
break; | |||||
} | |||||
stencilDepth = Mathf.Max(0, stencilDepth - 1); | |||||
var stencilId = (1 << stencilDepth) - 1; | |||||
mat.SetInt(ShaderId.STENCIL_ID, stencilId); | |||||
mat.SetInt(ShaderId.STENCIL_READ_MASK, stencilId); | |||||
} | |||||
MASK_MATERIALS_CACHE[(casterIsMask, baseMaterial)] = mat; | |||||
} | |||||
return mat; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: b035d21af0d3447296a96ea4f27b5f53 | |||||
timeCreated: 1600848777 |
@ -0,0 +1,189 @@ | |||||
using System; | |||||
using UnityEngine; | |||||
using UnityEngine.UI; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
[AddComponentMenu("")] | |||||
[ExecuteAlways] | |||||
public partial class ShadowRenderer : MonoBehaviour, ILayoutIgnorer, IMaterialModifier, IMeshModifier | |||||
{ | |||||
public bool ignoreLayout => true; | |||||
internal CanvasRenderer CanvasRenderer { get; private set; } | |||||
TrueShadow shadow; | |||||
RectTransform rt; | |||||
RawImage graphic; | |||||
Texture shadowTexture; | |||||
public static void Initialize(TrueShadow shadow, ref ShadowRenderer renderer) | |||||
{ | |||||
if (renderer && renderer.shadow == shadow) | |||||
{ | |||||
renderer.gameObject.SetActive(true); | |||||
return; | |||||
} | |||||
var obj = new GameObject($"{shadow.gameObject.name}'s Shadow") { | |||||
#if LETAI_TRUESHADOW_DEBUG | |||||
hideFlags = DebugSettings.Instance.showObjects | |||||
? HideFlags.DontSave | |||||
: HideFlags.HideAndDontSave | |||||
#else | |||||
hideFlags = HideFlags.HideAndDontSave | |||||
#endif | |||||
}; | |||||
#if LETAI_TRUESHADOW_DEBUG && UNITY_EDITOR | |||||
UnityEditor.SceneVisibilityManager.instance.DisablePicking(obj, true); | |||||
#endif | |||||
shadow.SetHierachyDirty(); | |||||
var rt = obj.AddComponent<RectTransform>(); | |||||
rt.anchorMin = Vector2.zero; | |||||
rt.anchorMax = Vector2.zero; | |||||
var graphic = obj.AddComponent<RawImage>(); | |||||
graphic.raycastTarget = false; | |||||
graphic.color = shadow.Color; | |||||
renderer = obj.AddComponent<ShadowRenderer>(); | |||||
renderer.shadow = shadow; | |||||
renderer.rt = rt; | |||||
renderer.graphic = graphic; | |||||
// renderer.RecreateGraphic(shadow.Baked ? GraphicType.Image : GraphicType.RawImage); | |||||
renderer.UpdateMaterial(); | |||||
renderer.CanvasRenderer = obj.GetComponent<CanvasRenderer>(); | |||||
renderer.CanvasRenderer.SetColor(shadow.IgnoreCasterColor ? Color.white : shadow.CanvasRenderer.GetColor()); | |||||
renderer.CanvasRenderer.SetAlpha(shadow.CanvasRenderer.GetAlpha()); | |||||
renderer.ReLayout(); | |||||
} | |||||
public void UpdateMaterial() | |||||
{ | |||||
var mat = shadow.BlendMode.GetMaterial(); | |||||
graphic.material = mat ? mat : shadow.GetShadowRenderingNormalMaterial(); | |||||
} | |||||
internal void ReLayout() | |||||
{ | |||||
if (!isActiveAndEnabled) | |||||
return; | |||||
var casterRt = shadow.RectTransform; | |||||
if (!casterRt) | |||||
{ | |||||
CanvasRenderer.SetAlpha(0); | |||||
return; | |||||
} | |||||
if (!shadowTexture) | |||||
{ | |||||
CanvasRenderer.SetAlpha(0); | |||||
return; | |||||
} | |||||
var container = shadow.ShadowContainer; | |||||
var canvasScale = container?.Snapshot?.canvasScale ?? graphic.canvas.scaleFactor; | |||||
var shadowTexSize = new Vector2(shadowTexture.width, shadowTexture.height) / canvasScale; | |||||
rt.sizeDelta = shadowTexSize; | |||||
// pivot should be relative to the un-blurred part of the texture, not the whole mesh | |||||
var casterPivotLS = -(Vector2) shadow.SpriteMesh.bounds.min; | |||||
var padding = (container?.Padding ?? Mathf.CeilToInt(shadow.Size * canvasScale)) / canvasScale * Vector2.one; | |||||
var misalign = container?.PxMisalignmentAtMinLS ?? Vector2.zero; | |||||
rt.pivot = (casterPivotLS + padding + misalign) / shadowTexSize; | |||||
var canvasRelativeOffset = container?.Snapshot?.canvasRelativeOffset / canvasScale ?? shadow.Offset; | |||||
var offset = shadow.ShadowAsSibling | |||||
? shadow.Offset.WithZ(0) | |||||
: canvasRelativeOffset.WithZ(0); | |||||
rt.localPosition = shadow.ShadowAsSibling | |||||
? casterRt.localPosition + offset | |||||
: offset; | |||||
rt.localRotation = shadow.ShadowAsSibling ? casterRt.localRotation : Quaternion.identity; | |||||
rt.localScale = shadow.ShadowAsSibling ? casterRt.localScale : Vector3.one; | |||||
var color = shadow.Color; | |||||
if (shadow.UseCasterAlpha) | |||||
color.a *= shadow.Graphic.color.a; | |||||
graphic.color = color; | |||||
CanvasRenderer.SetColor(shadow.IgnoreCasterColor ? Color.white : shadow.CanvasRenderer.GetColor()); | |||||
CanvasRenderer.SetAlpha(shadow.CanvasRenderer.GetAlpha()); | |||||
graphic.Rebuild(CanvasUpdate.PreRender); | |||||
} | |||||
public void SetTexture(Texture texture) | |||||
{ | |||||
shadowTexture = texture; | |||||
CanvasRenderer.SetTexture(texture); | |||||
graphic.texture = texture; | |||||
} | |||||
public void SetMaterialDirty() | |||||
{ | |||||
graphic.SetMaterialDirty(); | |||||
} | |||||
public void ModifyMesh(VertexHelper vertexHelper) | |||||
{ | |||||
if (!shadow) | |||||
return; | |||||
shadow.ModifyShadowRendererMesh(vertexHelper); | |||||
} | |||||
public void ModifyMesh(Mesh mesh) | |||||
{ | |||||
Debug.Assert(true, "This should only be called on old unsupported Unity version"); | |||||
} | |||||
protected virtual void LateUpdate() | |||||
{ | |||||
// Destroy events are not consistently called for some reason, have to poll | |||||
if (!shadow) | |||||
Dispose(); | |||||
} | |||||
bool willBeDestroyed; | |||||
protected virtual void OnDestroy() | |||||
{ | |||||
willBeDestroyed = true; | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
if (willBeDestroyed) return; | |||||
if (shadow && shadow.ShadowAsSibling) | |||||
{ | |||||
// Destroy does not happen immediately. Want out of hierarchy. | |||||
gameObject.SetActive(false); | |||||
transform.SetParent(null); | |||||
} | |||||
#if UNITY_EDITOR | |||||
// This look redundant but is necessary! | |||||
if (!Application.isPlaying && !UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) | |||||
DestroyImmediate(gameObject); | |||||
else if (Application.isPlaying) | |||||
Destroy(gameObject); | |||||
#else | |||||
Destroy(gameObject); | |||||
#endif | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 8c213e24c3734f43b893d039b31ac5bc | |||||
timeCreated: 1592813121 |
@ -0,0 +1,139 @@ | |||||
using System; | |||||
using UnityEngine; | |||||
using UnityEngine.UI; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
class ShadowSettingSnapshot | |||||
{ | |||||
public readonly TrueShadow shadow; | |||||
public readonly Canvas canvas; | |||||
public readonly RectTransform canvasRt; | |||||
public readonly bool shouldAntialiasImprint; | |||||
public readonly float canvasScale; | |||||
public readonly float size; | |||||
public readonly Vector2 canvasRelativeOffset; | |||||
public readonly Vector2 dimensions; | |||||
internal ShadowSettingSnapshot(TrueShadow shadow) | |||||
{ | |||||
this.shadow = shadow; | |||||
canvas = shadow.Graphic.canvas; | |||||
canvasRt = (RectTransform) canvas.transform; | |||||
var meshBound = shadow.SpriteMesh.bounds; | |||||
shouldAntialiasImprint = canvas.renderMode != RenderMode.ScreenSpaceOverlay; | |||||
canvasScale = canvas.scaleFactor; | |||||
var canvasRelativeRotation = Quaternion.Inverse(canvasRt.rotation) * shadow.RectTransform.rotation; | |||||
canvasRelativeOffset = shadow.Offset.Rotate(-canvasRelativeRotation.eulerAngles.z) * canvasScale; | |||||
dimensions = (Vector2) meshBound.size * canvasScale; | |||||
size = shadow.Size * canvasScale; | |||||
CalcHash(); | |||||
} | |||||
const int DIMENSIONS_HASH_STEP = 1; | |||||
void CalcHash() | |||||
{ | |||||
var graphic = shadow.Graphic; | |||||
int canvasScaleHash = (int) (canvasScale * 1e4); | |||||
int insetHash = shadow.Inset ? 1 : 0; | |||||
var clearColor = shadow.ClearColor; | |||||
var imageColor = graphic.color; | |||||
int colorHash = HashUtils.CombineHashCodes( | |||||
shadow.IgnoreCasterColor ? 1 : 0, | |||||
(int) shadow.ColorBleedMode, | |||||
(int) (imageColor.r * 255), | |||||
(int) (imageColor.g * 255), | |||||
(int) (imageColor.b * 255), | |||||
(int) (imageColor.a * 255), | |||||
(int) (clearColor.r * 255), | |||||
(int) (clearColor.g * 255), | |||||
(int) (clearColor.b * 255), | |||||
(int) (clearColor.a * 255) | |||||
); | |||||
// Hack until we have separated cutout cache, or proper sibling mode | |||||
int offsetHash = HashUtils.CombineHashCodes( | |||||
shadow.Cutout ? 1 : 0, | |||||
(int) (canvasRelativeOffset.x * 100), | |||||
(int) (canvasRelativeOffset.y * 100) | |||||
); | |||||
// Tiled type cannot be batched by similar size | |||||
int dimensionHash = graphic is Image im && im.type == Image.Type.Tiled | |||||
? dimensions.GetHashCode() | |||||
: HashUtils.CombineHashCodes( | |||||
Mathf.CeilToInt(dimensions.x / DIMENSIONS_HASH_STEP) * DIMENSIONS_HASH_STEP, | |||||
Mathf.CeilToInt(dimensions.y / DIMENSIONS_HASH_STEP) * DIMENSIONS_HASH_STEP | |||||
); | |||||
var sizeHash = Mathf.CeilToInt(size * 100); | |||||
var commonHash = HashUtils.CombineHashCodes( | |||||
shadow.TextureRevision, | |||||
canvasScaleHash, | |||||
insetHash, | |||||
colorHash, | |||||
offsetHash, | |||||
dimensionHash, | |||||
sizeHash | |||||
); | |||||
switch (graphic) | |||||
{ | |||||
case Image image: | |||||
int spriteHash = 0; | |||||
if (image.sprite) | |||||
spriteHash = image.sprite.GetHashCode(); | |||||
int imageHash = HashUtils.CombineHashCodes( | |||||
(int) image.type, | |||||
(int) (image.fillAmount * 360 * 20), | |||||
(int) image.fillMethod, | |||||
image.fillOrigin, | |||||
image.fillClockwise ? 1 : 0 | |||||
); | |||||
hash = HashUtils.CombineHashCodes( | |||||
commonHash, | |||||
spriteHash, | |||||
imageHash | |||||
); | |||||
break; | |||||
case RawImage rawImage: | |||||
var textureHash = 0; | |||||
if (rawImage.texture) | |||||
textureHash = rawImage.texture.GetInstanceID(); | |||||
hash = HashUtils.CombineHashCodes( | |||||
commonHash, | |||||
textureHash | |||||
); | |||||
break; | |||||
default: | |||||
hash = commonHash; | |||||
break; | |||||
} | |||||
} | |||||
int hash; | |||||
// ReSharper disable once NonReadonlyMemberInGetHashCode | |||||
public override int GetHashCode() => hash; | |||||
public override bool Equals(object obj) | |||||
{ | |||||
if (obj == null) return false; | |||||
return GetHashCode() == obj.GetHashCode(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 93693e4b8ffcbca48bc39fba2e37ffda | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,193 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
[ExecuteAlways] | |||||
public class ShadowSorter : MonoBehaviour | |||||
{ | |||||
#region SortDataContainer | |||||
readonly struct SortEntry : IComparable<SortEntry> | |||||
{ | |||||
public readonly TrueShadow shadow; | |||||
public readonly Transform shadowTransform; | |||||
public readonly Transform rendererTransform; | |||||
public SortEntry(TrueShadow shadow) | |||||
{ | |||||
this.shadow = shadow; | |||||
shadowTransform = shadow.transform; | |||||
rendererTransform = shadow.shadowRenderer.transform; | |||||
} | |||||
public int CompareTo(SortEntry other) | |||||
{ | |||||
return other.shadowTransform.GetSiblingIndex().CompareTo(shadowTransform.GetSiblingIndex()); | |||||
} | |||||
} | |||||
readonly struct SortGroup | |||||
{ | |||||
public readonly Transform parentTransform; | |||||
public readonly List<SortEntry> sortEntries; | |||||
public SortGroup(SortEntry firstEntry) | |||||
{ | |||||
sortEntries = new List<SortEntry> {firstEntry}; | |||||
parentTransform = firstEntry.shadowTransform.parent; | |||||
} | |||||
public void Add(SortEntry pair) | |||||
{ | |||||
if (pair.shadowTransform.parent != parentTransform) | |||||
return; | |||||
var index = sortEntries.BinarySearch(pair); | |||||
if (index < 0) | |||||
sortEntries.Insert(~index, pair); | |||||
} | |||||
public override int GetHashCode() | |||||
{ | |||||
return parentTransform.GetHashCode(); | |||||
} | |||||
public override bool Equals(object obj) | |||||
{ | |||||
return obj is SortGroup other && other.parentTransform == parentTransform; | |||||
} | |||||
} | |||||
#endregion | |||||
private static ShadowSorter instance; | |||||
public static ShadowSorter Instance | |||||
{ | |||||
get | |||||
{ | |||||
if (!instance) | |||||
{ | |||||
var existings = FindObjectsOfType<ShadowSorter>(); | |||||
for (int i = existings.Length - 1; i > 0; i--) | |||||
{ | |||||
Destroy(existings[i]); | |||||
} | |||||
#if UNITY_EDITOR | |||||
var hidden = GameObject.Find("/" + nameof(ShadowSorter)); | |||||
while (hidden) | |||||
{ | |||||
DestroyImmediate(hidden); | |||||
hidden = GameObject.Find("/" + nameof(ShadowSorter)); | |||||
} | |||||
#endif | |||||
instance = existings.Length > 0 ? existings[0] : null; | |||||
if (!instance) | |||||
{ | |||||
var obj = new GameObject(nameof(ShadowSorter)) { | |||||
#if LETAI_TRUESHADOW_DEBUG | |||||
hideFlags = DebugSettings.Instance.showObjects | |||||
? HideFlags.DontSave | |||||
: HideFlags.HideAndDontSave | |||||
#else | |||||
hideFlags = HideFlags.HideAndDontSave | |||||
#endif | |||||
}; | |||||
instance = obj.AddComponent<ShadowSorter>(); | |||||
} | |||||
} | |||||
return instance; | |||||
} | |||||
} | |||||
readonly IndexedSet<TrueShadow> shadows = new IndexedSet<TrueShadow>(); | |||||
readonly IndexedSet<SortGroup> sortGroups = new IndexedSet<SortGroup>(); | |||||
public void Register(TrueShadow shadow) | |||||
{ | |||||
shadows.AddUnique(shadow); | |||||
} | |||||
public void UnRegister(TrueShadow shadow) | |||||
{ | |||||
shadows.Remove(shadow); | |||||
} | |||||
void LateUpdate() | |||||
{ | |||||
if (!this) return; | |||||
for (var i = 0; i < shadows.Count; i++) | |||||
{ | |||||
var shadow = shadows[i]; | |||||
if (!shadow || !shadow.isActiveAndEnabled) | |||||
continue; | |||||
shadow.CheckHierarchyDirtied(); | |||||
if (shadow.hierachyDirty) | |||||
AddSortEntry(shadow); | |||||
} | |||||
Sort(); | |||||
} | |||||
void AddSortEntry(TrueShadow shadow) | |||||
{ | |||||
var entry = new SortEntry(shadow); | |||||
var group = new SortGroup(entry); | |||||
var oldIndex = sortGroups.IndexOf(group); | |||||
if (oldIndex > -1) | |||||
sortGroups[oldIndex].Add(entry); | |||||
else | |||||
sortGroups.Add(group); | |||||
} | |||||
public void Sort() | |||||
{ | |||||
for (var i = 0; i < sortGroups.Count; i++) | |||||
{ | |||||
var group = sortGroups[i]; | |||||
if (!group.parentTransform) | |||||
continue; | |||||
foreach (var entry in group.sortEntries) | |||||
{ | |||||
entry.rendererTransform.SetParent(group.parentTransform, false); | |||||
var rendererSid = entry.rendererTransform.GetSiblingIndex(); | |||||
var shadowSid = entry.shadowTransform.GetSiblingIndex(); | |||||
if (rendererSid > shadowSid) | |||||
{ | |||||
entry.rendererTransform.SetSiblingIndex(shadowSid); | |||||
} | |||||
else | |||||
{ | |||||
entry.rendererTransform.SetSiblingIndex(shadowSid - 1); | |||||
} | |||||
entry.shadow.UnSetHierachyDirty(); | |||||
} | |||||
// This is a separated loop, as siblind index of an entry will be affected by the laters | |||||
foreach (var entry in group.sortEntries) | |||||
{ | |||||
entry.shadow.ForgetSiblingIndexChanges(); | |||||
} | |||||
} | |||||
sortGroups.Clear(); | |||||
} | |||||
void OnApplicationQuit() | |||||
{ | |||||
// make sure object are recreated when exit play mode. Otherwise it turn into some weird state. need more research | |||||
Destroy(gameObject); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
fileFormatVersion: 2 | |||||
guid: cbf4f3c805fa455abb21fbb9ad6e54d5 | |||||
MonoImporter: | |||||
externalObjects: {} | |||||
serializedVersion: 2 | |||||
defaultReferences: [] | |||||
executionOrder: 0 | |||||
icon: {instanceID: 0} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,8 @@ | |||||
fileFormatVersion: 2 | |||||
guid: f395e653641dd7442bba503cb28b07a0 | |||||
folderAsset: yes | |||||
DefaultImporter: | |||||
externalObjects: {} | |||||
userData: | |||||
assetBundleName: | |||||
assetBundleVariant: |
@ -0,0 +1,42 @@ | |||||
using System; | |||||
using UnityEngine; | |||||
namespace LeTai.TrueShadow | |||||
{ | |||||
public enum BlendMode | |||||
{ | |||||
Normal, | |||||
Additive, | |||||
Screen, | |||||
Multiply, | |||||
} | |||||
public static class BlendModeExtensions | |||||
{ | |||||
static Material matNormal; | |||||
static Material materialAdditive; | |||||
static Material matScreen; | |||||
static Material matMultiply; | |||||
public static Material GetMaterial(this BlendMode blendMode) | |||||
{ | |||||
switch (blendMode) | |||||
{ | |||||
case BlendMode.Normal: | |||||
if (!matNormal) matNormal = new Material(Shader.Find("UI/TrueShadow-Normal")); | |||||
return matNormal; | |||||
case BlendMode.Additive: | |||||
if (!materialAdditive) materialAdditive = new Material(Shader.Find("UI/TrueShadow-Additive")); | |||||
return materialAdditive; | |||||
case BlendMode.Screen: | |||||
if (!matScreen) matScreen = new Material(Shader.Find("UI/TrueShadow-Screen")); | |||||
return matScreen; | |||||
case BlendMode.Multiply: | |||||
if (!matMultiply) matMultiply = new Material(Shader.Find("UI/TrueShadow-Multiply")); | |||||
return matMultiply; | |||||
default: | |||||
throw new ArgumentOutOfRangeException(); | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,3 @@ | |||||
fileFormatVersion: 2 | |||||
guid: 3379d45961064912b9e67abda88f84da | |||||
timeCreated: 1594095942 |