mirror of
https://github.com/drewcassidy/KSP-Conformal-Decals.git
synced 2024-09-01 18:23:54 +00:00
Big refactor to enable preview materials
• add shader variants for decal previewing • start to add code for part icon • refactor material properties to be serializable todo: • fix decal preview scale (need to call UpdateScale on detached state) • fix texture preview in part icon • adjust culling per-object when rendering (turns out cull and ztest values are used by unity at the render time, not by the shader itself, so they can be adjusted in material property blocks!)
This commit is contained in:
parent
6c20675a99
commit
e9c8f3dafb
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,6 +10,9 @@ Logs/
|
|||||||
Packages/
|
Packages/
|
||||||
ProjectSettings/
|
ProjectSettings/
|
||||||
Temp/
|
Temp/
|
||||||
|
Distribution/GameData/ConformalDecals/Resources/Resources
|
||||||
|
Distribution/GameData/ConformalDecals/Resources/Resources.manifest
|
||||||
|
Distribution/GameData/ConformalDecals/Resources/conformaldecals.shab.manifest
|
||||||
|
|
||||||
# Unity Project Files
|
# Unity Project Files
|
||||||
PartTools.cfg
|
PartTools.cfg
|
||||||
|
@ -4,10 +4,14 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
{
|
{
|
||||||
[Header(Texture Maps)]
|
[Header(Texture Maps)]
|
||||||
_Decal("Decal Texture", 2D) = "gray" {}
|
_Decal("Decal Texture", 2D) = "gray" {}
|
||||||
_BumpMap("Decal Bump Map", 2D) = "bump" {}
|
_DecalBumpMap("Decal Bump Map", 2D) = "bump" {}
|
||||||
|
|
||||||
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
||||||
_Opacity("_Opacity", Range(0,1) ) = 1
|
_DecalOpacity("Opacity", Range(0,1) ) = 1
|
||||||
|
_Background("Background Color", Color) = (0.9,0.9,0.9,0.7)
|
||||||
|
|
||||||
|
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0
|
||||||
|
[Toggle(DECAL_PREVIEW)] _Preview ("Preview", Float) = 0
|
||||||
|
|
||||||
[Header(Effects)]
|
[Header(Effects)]
|
||||||
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
||||||
@ -18,9 +22,9 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
SubShader
|
SubShader
|
||||||
{
|
{
|
||||||
Tags { "Queue" = "Geometry+100" }
|
Tags { "Queue" = "Geometry+100" }
|
||||||
Cull Off
|
Cull [_Cull]
|
||||||
Zwrite Off
|
|
||||||
Ztest LEqual
|
Ztest LEqual
|
||||||
|
|
||||||
Pass
|
Pass
|
||||||
{
|
{
|
||||||
Name "FORWARD"
|
Name "FORWARD"
|
||||||
@ -32,15 +36,14 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile DECAL_PROJECT DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
sampler2D _DecalBumpMap;
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
float4 _DecalBumpMap_ST;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -55,16 +58,18 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_bump));
|
float3 normal = UnpackNormal(tex2D(_DecalBumpMap, IN.uv_bump));
|
||||||
|
|
||||||
|
#ifdef DECAL_PROJECT
|
||||||
// clip alpha
|
// clip alpha
|
||||||
clip(color.a - saturate(_Cutoff + 0.01));
|
clip(color.a - _Cutoff + 0.01);
|
||||||
|
#endif //DECAL_PROJECT
|
||||||
|
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a * _Opacity;
|
o.Alpha = color.a * _DecalOpacity;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
o.Normal = normal;
|
||||||
}
|
}
|
||||||
@ -83,15 +88,14 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile DECAL_PROJECT DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
sampler2D _DecalBumpMap;
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
float4 _DecalBumpMap_ST;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -106,16 +110,18 @@ Shader "ConformalDecals/Feature/Bumped"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_bump));
|
float3 normal = UnpackNormal(tex2D(_DecalBumpMap, IN.uv_bump));
|
||||||
|
|
||||||
|
#ifdef DECAL_PROJECT
|
||||||
// clip alpha
|
// clip alpha
|
||||||
clip(color.a - saturate(_Cutoff + 0.01));
|
clip(color.a - _Cutoff + 0.01);
|
||||||
|
#endif //DECAL_PROJECT
|
||||||
|
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a * _Opacity;
|
o.Alpha = color.a * _DecalOpacity;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
o.Normal = normal;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,15 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
_Decal("Decal Texture", 2D) = "gray" {}
|
_Decal("Decal Texture", 2D) = "gray" {}
|
||||||
_BumpMap("Bump Map", 2D) = "bump" {}
|
_BumpMap("Bump Map", 2D) = "bump" {}
|
||||||
|
|
||||||
_EdgeWearStrength("Edge Wear Strength", Range(0,100)) = 0
|
_EdgeWearStrength("Edge Wear Strength", Range(0,500)) = 100
|
||||||
_EdgeWearOffset("Edge Wear Offset", Range(0,1)) = 0
|
_EdgeWearOffset("Edge Wear Offset", Range(0,1)) = 0.1
|
||||||
|
|
||||||
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
||||||
_Opacity("_Opacity", Range(0,1) ) = 1
|
_DecalOpacity("Opacity", Range(0,1) ) = 1
|
||||||
|
_Background("Background Color", Color) = (0.9,0.9,0.9,0.7)
|
||||||
|
|
||||||
|
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0
|
||||||
|
[Toggle(DECAL_PREVIEW)] _Preview ("Preview", Float) = 0
|
||||||
|
|
||||||
[Header(Effects)]
|
[Header(Effects)]
|
||||||
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
||||||
@ -20,10 +24,9 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
}
|
}
|
||||||
SubShader
|
SubShader
|
||||||
{
|
{
|
||||||
Tags { "Queue" = "Geometry+400" }
|
Tags { "Queue" = "Geometry+100" }
|
||||||
ZWrite Off
|
Cull [_Cull]
|
||||||
ZTest LEqual
|
Ztest LEqual
|
||||||
Offset -1, -1
|
|
||||||
|
|
||||||
Pass
|
Pass
|
||||||
{
|
{
|
||||||
@ -36,18 +39,15 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile __ DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
|
||||||
|
|
||||||
float _EdgeWearStrength;
|
float _EdgeWearStrength;
|
||||||
float _EdgeWearOffset;
|
float _EdgeWearOffset;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -62,11 +62,10 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_base));
|
|
||||||
|
|
||||||
// clip alpha
|
decalClipAlpha(color.a);
|
||||||
clip(color.a - _Cutoff);
|
|
||||||
|
|
||||||
|
float3 normal = IN.normal;
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
|
|
||||||
@ -76,9 +75,8 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a * _Opacity;
|
o.Alpha = color.a * _DecalOpacity;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ENDCG
|
ENDCG
|
||||||
@ -95,18 +93,15 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile __ DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
|
||||||
|
|
||||||
float _EdgeWearStrength;
|
float _EdgeWearStrength;
|
||||||
float _EdgeWearOffset;
|
float _EdgeWearOffset;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -121,11 +116,10 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_base));
|
|
||||||
|
|
||||||
// clip alpha
|
decalClipAlpha(color.a);
|
||||||
clip(color.a - _Cutoff);
|
|
||||||
|
|
||||||
|
float3 normal = IN.normal;
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
|
|
||||||
@ -135,9 +129,8 @@ Shader "ConformalDecals/Paint/Diffuse"
|
|||||||
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a * _Opacity;
|
o.Alpha = color.a * _DecalOpacity;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ENDCG
|
ENDCG
|
||||||
|
@ -7,16 +7,19 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
_BumpMap("Bump Map", 2D) = "bump" {}
|
_BumpMap("Bump Map", 2D) = "bump" {}
|
||||||
_SpecMap("Specular Map", 2D) = "black" {}
|
_SpecMap("Specular Map", 2D) = "black" {}
|
||||||
|
|
||||||
_EdgeWearStrength("Edge Wear Strength", Range(0,100)) = 0
|
_EdgeWearStrength("Edge Wear Strength", Range(0,500)) = 100
|
||||||
_EdgeWearOffset("Edge Wear Offset", Range(0,1)) = 0
|
_EdgeWearOffset("Edge Wear Offset", Range(0,1)) = 0.1
|
||||||
|
|
||||||
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
|
||||||
_Opacity("_Opacity", Range(0,1) ) = 1
|
_DecalOpacity("Opacity", Range(0,1) ) = 1
|
||||||
|
_Background("Background Color", Color) = (0.9,0.9,0.9,0.7)
|
||||||
|
|
||||||
[Header(Specularity)]
|
[Header(Specularity)]
|
||||||
_SpecColor ("_SpecColor", Color) = (0.5, 0.5, 0.5, 1)
|
_SpecColor ("_SpecColor", Color) = (0.25, 0.25, 0.25, 1)
|
||||||
_Shininess ("Shininess", Range (0.03, 10)) = 0.4
|
_Shininess ("Shininess", Range (0.03, 10)) = 0.3
|
||||||
|
|
||||||
|
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0
|
||||||
|
[Toggle(DECAL_PREVIEW)] _Preview ("Preview", Float) = 0
|
||||||
|
|
||||||
[Header(Effects)]
|
[Header(Effects)]
|
||||||
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
|
||||||
@ -26,10 +29,9 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
}
|
}
|
||||||
SubShader
|
SubShader
|
||||||
{
|
{
|
||||||
Tags { "Queue" = "Geometry+400" }
|
Tags { "Queue" = "Geometry+100" }
|
||||||
ZWrite Off
|
Cull [_Cull]
|
||||||
ZTest LEqual
|
Ztest LEqual
|
||||||
Offset -1, -1
|
|
||||||
|
|
||||||
Pass
|
Pass
|
||||||
{
|
{
|
||||||
@ -42,13 +44,12 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile __ DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
|
||||||
sampler2D _SpecMap;
|
sampler2D _SpecMap;
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
|
||||||
float4 _SpecMap_ST;
|
float4 _SpecMap_ST;
|
||||||
|
|
||||||
float _EdgeWearStrength;
|
float _EdgeWearStrength;
|
||||||
@ -56,8 +57,6 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
|
|
||||||
half _Shininess;
|
half _Shininess;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -73,11 +72,13 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_base));
|
|
||||||
float3 specular = tex2D(_SpecMap, IN.uv_spec);
|
float3 specular = tex2D(_SpecMap, IN.uv_spec);
|
||||||
|
float3 normal = IN.normal;
|
||||||
|
|
||||||
|
#ifdef DECAL_PROJECT
|
||||||
// clip alpha
|
// clip alpha
|
||||||
clip(color.a - _Cutoff);
|
clip(color.a - _Cutoff + 0.01);
|
||||||
|
#endif //DECAL_PROJECT
|
||||||
|
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
@ -86,12 +87,11 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
float wearFactorAlpha = saturate(_EdgeWearStrength * wearFactor);
|
float wearFactorAlpha = saturate(_EdgeWearStrength * wearFactor);
|
||||||
|
|
||||||
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
||||||
color.a *= _Opacity;
|
color.a *= _DecalOpacity;
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a;
|
o.Alpha = color.a;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
|
||||||
o.Specular = _Shininess;
|
o.Specular = _Shininess;
|
||||||
o.Gloss = specular.r * color.a;
|
o.Gloss = specular.r * color.a;
|
||||||
}
|
}
|
||||||
@ -110,13 +110,12 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
#pragma fragment frag_forward
|
#pragma fragment frag_forward
|
||||||
|
|
||||||
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
|
||||||
|
#pragma multi_compile __ DECAL_PREVIEW
|
||||||
|
|
||||||
sampler2D _Decal;
|
sampler2D _Decal;
|
||||||
sampler2D _BumpMap;
|
|
||||||
sampler2D _SpecMap;
|
sampler2D _SpecMap;
|
||||||
|
|
||||||
float4 _Decal_ST;
|
float4 _Decal_ST;
|
||||||
float4 _BumpMap_ST;
|
|
||||||
float4 _SpecMap_ST;
|
float4 _SpecMap_ST;
|
||||||
|
|
||||||
float _EdgeWearStrength;
|
float _EdgeWearStrength;
|
||||||
@ -124,8 +123,6 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
|
|
||||||
half _Shininess;
|
half _Shininess;
|
||||||
|
|
||||||
float _Cutoff;
|
|
||||||
float _Opacity;
|
|
||||||
float _RimFalloff;
|
float _RimFalloff;
|
||||||
float4 _RimColor;
|
float4 _RimColor;
|
||||||
|
|
||||||
@ -141,11 +138,13 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o)
|
||||||
{
|
{
|
||||||
float4 color = tex2D(_Decal, IN.uv_decal);
|
float4 color = tex2D(_Decal, IN.uv_decal);
|
||||||
float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_base));
|
|
||||||
float3 specular = tex2D(_SpecMap, IN.uv_spec);
|
float3 specular = tex2D(_SpecMap, IN.uv_spec);
|
||||||
|
float3 normal = IN.normal;
|
||||||
|
|
||||||
|
#ifdef DECAL_PROJECT
|
||||||
// clip alpha
|
// clip alpha
|
||||||
clip(color.a - _Cutoff);
|
clip(color.a - _Cutoff + 0.01);
|
||||||
|
#endif //DECAL_PROJECT
|
||||||
|
|
||||||
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
half rim = 1.0 - saturate(dot (normalize(IN.viewDir), normal));
|
||||||
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
float3 emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
|
||||||
@ -156,9 +155,8 @@ Shader "ConformalDecals/Paint/Specular"
|
|||||||
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
color.a *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
|
||||||
|
|
||||||
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
|
||||||
o.Alpha = color.a * _Opacity;
|
o.Alpha = color.a * _DecalOpacity;
|
||||||
o.Emission = emission;
|
o.Emission = emission;
|
||||||
o.Normal = normal;
|
|
||||||
o.Specular = _Shininess;
|
o.Specular = _Shininess;
|
||||||
o.Gloss = specular.r;
|
o.Gloss = specular.r;
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,9 @@ struct DecalSurfaceInput
|
|||||||
#endif //DECAL_EMISSIVE
|
#endif //DECAL_EMISSIVE
|
||||||
|
|
||||||
#ifdef DECAL_BASE_NORMAL
|
#ifdef DECAL_BASE_NORMAL
|
||||||
float2 uv_base;
|
|
||||||
#endif //DECAL_BASE_NORMAL
|
|
||||||
|
|
||||||
float3 normal;
|
float3 normal;
|
||||||
|
#endif
|
||||||
|
|
||||||
float3 viewDir;
|
float3 viewDir;
|
||||||
float3 worldPosition;
|
float3 worldPosition;
|
||||||
};
|
};
|
||||||
@ -30,10 +29,10 @@ struct appdata_decal
|
|||||||
{
|
{
|
||||||
float4 vertex : POSITION;
|
float4 vertex : POSITION;
|
||||||
float3 normal : NORMAL;
|
float3 normal : NORMAL;
|
||||||
#ifdef DECAL_BASE_NORMAL
|
#if defined(DECAL_BASE_NORMAL) || defined(DECAL_PREVIEW)
|
||||||
float4 texcoord : TEXCOORD0;
|
float4 texcoord : TEXCOORD0;
|
||||||
float4 tangent : TANGENT;
|
float4 tangent : TANGENT;
|
||||||
#endif //DECAL_BASE_NORMAL
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct v2f
|
struct v2f
|
||||||
@ -65,6 +64,23 @@ float4x4 _ProjectionMatrix;
|
|||||||
float3 _DecalNormal;
|
float3 _DecalNormal;
|
||||||
float3 _DecalTangent;
|
float3 _DecalTangent;
|
||||||
|
|
||||||
|
#ifdef DECAL_BASE_NORMAL
|
||||||
|
sampler2D _BumpMap;
|
||||||
|
float4 _BumpMap_ST;
|
||||||
|
#endif //DECAL_BASE_NORMAL
|
||||||
|
|
||||||
|
float _Cutoff;
|
||||||
|
float _DecalOpacity;
|
||||||
|
|
||||||
|
float _Opacity;
|
||||||
|
float4 _Background;
|
||||||
|
|
||||||
|
inline void decalClipAlpha(float alpha) {
|
||||||
|
#ifndef DECAL_PREVIEW
|
||||||
|
clip(alpha - _Cutoff + 0.01);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// declare surf function,
|
// declare surf function,
|
||||||
// this must be defined in any shader using this cginc
|
// this must be defined in any shader using this cginc
|
||||||
void surf (DecalSurfaceInput IN, inout SurfaceOutput o);
|
void surf (DecalSurfaceInput IN, inout SurfaceOutput o);
|
||||||
@ -76,7 +92,12 @@ v2f vert_forward(appdata_decal v)
|
|||||||
|
|
||||||
o.pos = UnityObjectToClipPos(v.vertex);
|
o.pos = UnityObjectToClipPos(v.vertex);
|
||||||
o.normal = v.normal;
|
o.normal = v.normal;
|
||||||
|
|
||||||
|
#ifdef DECAL_PREVIEW
|
||||||
|
o.uv_decal = v.texcoord;
|
||||||
|
#else
|
||||||
o.uv_decal = mul (_ProjectionMatrix, v.vertex);
|
o.uv_decal = mul (_ProjectionMatrix, v.vertex);
|
||||||
|
#endif //DECAL_PREVIEW
|
||||||
|
|
||||||
#ifdef DECAL_BASE_NORMAL
|
#ifdef DECAL_BASE_NORMAL
|
||||||
o.uv_base = TRANSFORM_TEX(v.texcoord, _BumpMap);
|
o.uv_base = TRANSFORM_TEX(v.texcoord, _BumpMap);
|
||||||
@ -85,7 +106,7 @@ v2f vert_forward(appdata_decal v)
|
|||||||
float3 worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
|
float3 worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
|
||||||
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
|
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
|
||||||
|
|
||||||
#ifdef DECAL_BASE_NORMAL
|
#if defined(DECAL_BASE_NORMAL) || defined(DECAL_PREVIEW)
|
||||||
// use tangent of base geometry
|
// use tangent of base geometry
|
||||||
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
|
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
|
||||||
fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
|
fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
|
||||||
@ -95,7 +116,7 @@ v2f vert_forward(appdata_decal v)
|
|||||||
fixed3 decalTangent = UnityObjectToWorldDir(_DecalTangent);
|
fixed3 decalTangent = UnityObjectToWorldDir(_DecalTangent);
|
||||||
fixed3 worldBinormal = cross(decalTangent, worldNormal);
|
fixed3 worldBinormal = cross(decalTangent, worldNormal);
|
||||||
fixed3 worldTangent = cross(worldNormal, worldBinormal);
|
fixed3 worldTangent = cross(worldNormal, worldBinormal);
|
||||||
#endif //DECAL_BASE_NORMAL
|
#endif //defined(DECAL_BASE_NORMAL) || defined(DECAL_PREVIEW)
|
||||||
|
|
||||||
o.tSpace0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPosition.x);
|
o.tSpace0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPosition.x);
|
||||||
o.tSpace1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPosition.y);
|
o.tSpace1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPosition.y);
|
||||||
@ -148,6 +169,9 @@ fixed4 frag_forward(v2f IN) : SV_Target
|
|||||||
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPosition));
|
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPosition));
|
||||||
float3 viewDir = _unity_tbn_0 * worldViewDir.x + _unity_tbn_1 * worldViewDir.y + _unity_tbn_2 * worldViewDir.z;
|
float3 viewDir = _unity_tbn_0 * worldViewDir.x + _unity_tbn_1 * worldViewDir.y + _unity_tbn_2 * worldViewDir.z;
|
||||||
|
|
||||||
|
#ifdef DECAL_PREVIEW
|
||||||
|
fixed4 uv_projected = IN.uv_decal;
|
||||||
|
#else
|
||||||
// perform decal projection
|
// perform decal projection
|
||||||
fixed4 uv_projected = UNITY_PROJ_COORD(IN.uv_decal);
|
fixed4 uv_projected = UNITY_PROJ_COORD(IN.uv_decal);
|
||||||
|
|
||||||
@ -157,13 +181,14 @@ fixed4 frag_forward(v2f IN) : SV_Target
|
|||||||
|
|
||||||
// clip backsides
|
// clip backsides
|
||||||
clip(dot(_DecalNormal, IN.normal));
|
clip(dot(_DecalNormal, IN.normal));
|
||||||
|
#endif //DECAL_PREVIEW
|
||||||
|
|
||||||
// initialize surface input
|
// initialize surface input
|
||||||
UNITY_INITIALIZE_OUTPUT(DecalSurfaceInput, i)
|
UNITY_INITIALIZE_OUTPUT(DecalSurfaceInput, i)
|
||||||
i.uv_decal = TRANSFORM_TEX(uv_projected, _Decal);
|
i.uv_decal = TRANSFORM_TEX(uv_projected, _Decal);
|
||||||
|
|
||||||
#ifdef DECAL_NORMAL
|
#ifdef DECAL_NORMAL
|
||||||
i.uv_bump = TRANSFORM_TEX(uv_projected, _BumpMap);
|
i.uv_bump = TRANSFORM_TEX(uv_projected, _DecalBumpMap);
|
||||||
#endif //DECAL_NORMAL
|
#endif //DECAL_NORMAL
|
||||||
|
|
||||||
#ifdef DECAL_SPECULAR
|
#ifdef DECAL_SPECULAR
|
||||||
@ -175,10 +200,14 @@ fixed4 frag_forward(v2f IN) : SV_Target
|
|||||||
#endif //DECAL_EMISSIVE
|
#endif //DECAL_EMISSIVE
|
||||||
|
|
||||||
#ifdef DECAL_BASE_NORMAL
|
#ifdef DECAL_BASE_NORMAL
|
||||||
i.uv_base = IN.uv_base;
|
#ifdef DECAL_PREVIEW
|
||||||
|
i.normal = fixed3(0,0,1);
|
||||||
|
#else
|
||||||
|
i.normal = UnpackNormal(tex2D(_BumpMap, IN.uv_base));
|
||||||
|
#endif //DECAL_PREVIEW
|
||||||
#endif //DECAL_BASE_NORMAL
|
#endif //DECAL_BASE_NORMAL
|
||||||
|
|
||||||
i.normal = IN.normal;
|
//i.normal = IN.normal;
|
||||||
i.viewDir = viewDir;
|
i.viewDir = viewDir;
|
||||||
i.worldPosition = worldPosition;
|
i.worldPosition = worldPosition;
|
||||||
|
|
||||||
@ -193,6 +222,14 @@ fixed4 frag_forward(v2f IN) : SV_Target
|
|||||||
// call surface function
|
// call surface function
|
||||||
surf(i, o);
|
surf(i, o);
|
||||||
|
|
||||||
|
#ifdef DECAL_PREVIEW
|
||||||
|
o.Albedo = lerp(_Background.rgb,o.Albedo, o.Alpha);
|
||||||
|
o.Normal = lerp(float3(0,0,1), o.Normal, o.Alpha);
|
||||||
|
o.Gloss = lerp(_Background.a, o.Gloss, o.Alpha);
|
||||||
|
o.Emission = lerp(0, o.Emission, o.Alpha);
|
||||||
|
o.Alpha = _Opacity;
|
||||||
|
#endif //DECAL_PREVIEW
|
||||||
|
|
||||||
// compute lighting & shadowing factor
|
// compute lighting & shadowing factor
|
||||||
UNITY_LIGHT_ATTENUATION(atten, IN, worldPosition)
|
UNITY_LIGHT_ATTENUATION(atten, IN, worldPosition)
|
||||||
|
|
||||||
@ -204,7 +241,6 @@ fixed4 frag_forward(v2f IN) : SV_Target
|
|||||||
WorldNormal = normalize(WorldNormal);
|
WorldNormal = normalize(WorldNormal);
|
||||||
o.Normal = WorldNormal;
|
o.Normal = WorldNormal;
|
||||||
|
|
||||||
|
|
||||||
//KSP lighting function
|
//KSP lighting function
|
||||||
c += LightingBlinnPhongSmooth(o, lightDir, worldViewDir, atten);
|
c += LightingBlinnPhongSmooth(o, lightDir, worldViewDir, atten);
|
||||||
|
|
||||||
|
@ -64,18 +64,18 @@ PART
|
|||||||
name = ModuleConformalDecalGeneric
|
name = ModuleConformalDecalGeneric
|
||||||
}
|
}
|
||||||
DATA {
|
DATA {
|
||||||
decalShader = ConformalDecals/Feature/Bumped
|
shader = ConformalDecals/Feature/Bumped
|
||||||
|
|
||||||
TEXTURE {
|
TEXTURE {
|
||||||
name = _Decal
|
name = _Decal
|
||||||
textureURL = ConformalDecals/Assets/Tortilla-diffuse
|
textureUrl = ConformalDecals/Assets/Tortilla-diffuse
|
||||||
isMain = true
|
isMain = true
|
||||||
}
|
}
|
||||||
|
|
||||||
TEXTURE
|
TEXTURE
|
||||||
{
|
{
|
||||||
name = _BumpMap
|
name = _DecalBumpMap
|
||||||
textureURL = ConformalDecals/Assets/Tortilla-normal
|
textureUrl = ConformalDecals/Assets/Tortilla-normal
|
||||||
isNormalMap = true
|
isNormalMap = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,11 +90,11 @@ PART
|
|||||||
name = ModuleConformalDecalGeneric
|
name = ModuleConformalDecalGeneric
|
||||||
}
|
}
|
||||||
DATA {
|
DATA {
|
||||||
decalShader = ConformalDecals/Paint/Diffuse
|
shader = ConformalDecals/Paint/Diffuse
|
||||||
useBaseNormal = true
|
useBaseNormal = true
|
||||||
TEXTURE {
|
TEXTURE {
|
||||||
name = _Decal
|
name = _Decal
|
||||||
textureURL = ConformalDecals/Assets/Sign-HighVoltage-2
|
textureUrl = ConformalDecals/Assets/Sign-HighVoltage-2
|
||||||
isMain = true
|
isMain = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -3,18 +3,18 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace ConformalDecals.MaterialModifiers {
|
namespace ConformalDecals.MaterialModifiers {
|
||||||
public class MaterialColorProperty : MaterialProperty {
|
public class MaterialColorProperty : MaterialProperty {
|
||||||
private readonly Color _color;
|
[SerializeField] public Color color;
|
||||||
|
|
||||||
public MaterialColorProperty(ConfigNode node) : base(node) {
|
public override void ParseNode(ConfigNode node) {
|
||||||
_color = ParsePropertyColor(node, "color", false);
|
base.ParseNode(node);
|
||||||
}
|
|
||||||
|
|
||||||
public MaterialColorProperty(string name, Color value) : base(name) {
|
color = ParsePropertyColor(node, "color", true, color);
|
||||||
_color = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Modify(Material material) {
|
public override void Modify(Material material) {
|
||||||
material.SetColor(_propertyID, _color);
|
if (material == null) throw new ArgumentNullException("material cannot be null");
|
||||||
|
|
||||||
|
material.SetColor(_propertyID, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,18 +3,18 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace ConformalDecals.MaterialModifiers {
|
namespace ConformalDecals.MaterialModifiers {
|
||||||
public class MaterialFloatProperty : MaterialProperty {
|
public class MaterialFloatProperty : MaterialProperty {
|
||||||
private readonly float _value;
|
[SerializeField] public float value;
|
||||||
|
|
||||||
public MaterialFloatProperty(ConfigNode node) : base(node) {
|
public override void ParseNode(ConfigNode node) {
|
||||||
_value = ParsePropertyFloat(node, "value", false);
|
base.ParseNode(node);
|
||||||
}
|
|
||||||
|
|
||||||
public MaterialFloatProperty(string name, float value) : base(name) {
|
value = ParsePropertyFloat(node, "value", true, value);
|
||||||
_value = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Modify(Material material) {
|
public override void Modify(Material material) {
|
||||||
material.SetFloat(_propertyID, _value);
|
if (material == null) throw new ArgumentNullException("material cannot be null");
|
||||||
|
|
||||||
|
material.SetFloat(_propertyID, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,24 @@
|
|||||||
using System;
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace ConformalDecals.MaterialModifiers {
|
namespace ConformalDecals.MaterialModifiers {
|
||||||
public abstract class MaterialProperty {
|
public abstract class MaterialProperty : ScriptableObject {
|
||||||
public string PropertyName { get; }
|
public string Name {
|
||||||
|
get => _propertyName;
|
||||||
|
set {
|
||||||
|
_propertyName = value;
|
||||||
|
_propertyID = Shader.PropertyToID(_propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected readonly int _propertyID;
|
[SerializeField] protected int _propertyID;
|
||||||
|
[SerializeField] protected string _propertyName;
|
||||||
|
|
||||||
|
public virtual void ParseNode(ConfigNode node) {
|
||||||
|
if (node == null) throw new ArgumentNullException("node cannot be null");
|
||||||
|
|
||||||
protected MaterialProperty(ConfigNode node) : this(node.GetValue("name")) { }
|
Name = node.GetValue("name");
|
||||||
|
|
||||||
protected MaterialProperty(string name) {
|
|
||||||
if (name == null)
|
|
||||||
throw new FormatException("name not found, cannot create material modifier");
|
|
||||||
|
|
||||||
if (name == string.Empty)
|
|
||||||
throw new FormatException("name is empty, cannot create material modifier");
|
|
||||||
|
|
||||||
PropertyName = name;
|
|
||||||
_propertyID = Shader.PropertyToID(PropertyName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void Modify(Material material);
|
public abstract void Modify(Material material);
|
||||||
@ -57,10 +57,10 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (valueString == null)
|
if (valueString == null)
|
||||||
throw new FormatException($"Missing {typeof(T)} value for {valueName} in property '{PropertyName}'");
|
throw new FormatException($"Missing {typeof(T)} value for {valueName} in property '{Name}'");
|
||||||
|
|
||||||
if (valueString == string.Empty)
|
if (valueString == string.Empty)
|
||||||
throw new FormatException($"Empty {typeof(T)} value for {valueName} in property '{PropertyName}'");
|
throw new FormatException($"Empty {typeof(T)} value for {valueName} in property '{Name}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tryParse(valueString, out var value)) {
|
if (tryParse(valueString, out var value)) {
|
||||||
@ -72,7 +72,7 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} in property '{PropertyName}' : '{valueString}");
|
throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} in property '{Name}' : '{valueString}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,18 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace ConformalDecals.MaterialModifiers {
|
namespace ConformalDecals.MaterialModifiers {
|
||||||
public class MaterialPropertyCollection : ScriptableObject {
|
public class MaterialPropertyCollection : ScriptableObject {
|
||||||
private static readonly int OpacityId = Shader.PropertyToID("_Opacity");
|
private static readonly int OpacityId = Shader.PropertyToID("_DecalOpacity");
|
||||||
private static readonly int CutoffId = Shader.PropertyToID("_Cutoff");
|
private static readonly int CutoffId = Shader.PropertyToID("_Cutoff");
|
||||||
|
|
||||||
public MaterialTextureProperty MainMaterialTextureProperty { get; set; }
|
public MaterialTextureProperty MainMaterialTextureProperty => _mainTexture;
|
||||||
|
|
||||||
private List<MaterialProperty> _materialProperties;
|
public Shader DecalShader => _shader;
|
||||||
private List<MaterialTextureProperty> _textureMaterialProperties;
|
|
||||||
|
|
||||||
public Material DecalMaterial {
|
public Material DecalMaterial {
|
||||||
get {
|
get {
|
||||||
|
Debug.Log($"{_textureMaterialProperties == null}");
|
||||||
if (_decalMaterial == null) {
|
if (_decalMaterial == null) {
|
||||||
_decalMaterial = new Material(_decalShader);
|
_decalMaterial = new Material(_shader);
|
||||||
UpdateMaterial(_decalMaterial);
|
UpdateMaterial(_decalMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,13 +23,32 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Shader DecalShader => _decalShader;
|
public Material PreviewMaterial {
|
||||||
|
get {
|
||||||
|
if (_previewMaterial == null) {
|
||||||
|
_previewMaterial = new Material(_shader);
|
||||||
|
UpdateMaterial(_previewMaterial);
|
||||||
|
_previewMaterial.EnableKeyword("DECAL_PREVIEW");
|
||||||
|
}
|
||||||
|
|
||||||
public float AspectRatio => MainMaterialTextureProperty?.AspectRatio ?? 1f;
|
return _previewMaterial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[SerializeField] private Shader _decalShader;
|
public float AspectRatio {
|
||||||
|
get {
|
||||||
|
if (MainMaterialTextureProperty == null) return 1;
|
||||||
|
return MainMaterialTextureProperty.AspectRatio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SerializeField] private Shader _shader;
|
||||||
|
[SerializeField] private List<MaterialProperty> _materialProperties;
|
||||||
|
[SerializeField] private List<MaterialTextureProperty> _textureMaterialProperties;
|
||||||
|
[SerializeField] private MaterialTextureProperty _mainTexture;
|
||||||
|
|
||||||
private Material _decalMaterial;
|
private Material _decalMaterial;
|
||||||
|
private Material _previewMaterial;
|
||||||
|
|
||||||
public void Initialize() {
|
public void Initialize() {
|
||||||
_materialProperties = new List<MaterialProperty>();
|
_materialProperties = new List<MaterialProperty>();
|
||||||
@ -44,7 +63,7 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var p in _materialProperties) {
|
foreach (var p in _materialProperties) {
|
||||||
if (p.PropertyName == property.PropertyName) {
|
if (p.Name == property.Name) {
|
||||||
_materialProperties.Remove(property);
|
_materialProperties.Remove(property);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,20 +72,20 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
|
|
||||||
if (property is MaterialTextureProperty textureProperty) {
|
if (property is MaterialTextureProperty textureProperty) {
|
||||||
foreach (var p in _textureMaterialProperties) {
|
foreach (var p in _textureMaterialProperties) {
|
||||||
if (p.PropertyName == textureProperty.PropertyName) {
|
if (p.Name == textureProperty.Name) {
|
||||||
_textureMaterialProperties.Remove(textureProperty);
|
_textureMaterialProperties.Remove(textureProperty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_textureMaterialProperties.Add(textureProperty);
|
_textureMaterialProperties.Add(textureProperty);
|
||||||
|
|
||||||
if (textureProperty.IsMain) MainMaterialTextureProperty ??= textureProperty;
|
if (textureProperty.isMain) _mainTexture ??= textureProperty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetShader(string shaderName) {
|
public void SetShader(string shaderName) {
|
||||||
if (string.IsNullOrEmpty(shaderName)) {
|
if (string.IsNullOrEmpty(shaderName)) {
|
||||||
if (_decalShader == null) {
|
if (_shader == null) {
|
||||||
Debug.Log("Using default decal shader");
|
Debug.Log("Using default decal shader");
|
||||||
shaderName = "ConformalDecals/Paint/Diffuse";
|
shaderName = "ConformalDecals/Paint/Diffuse";
|
||||||
}
|
}
|
||||||
@ -79,7 +98,9 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
|
|
||||||
if (shader == null) throw new FormatException($"Unable to find specified shader '{shaderName}'");
|
if (shader == null) throw new FormatException($"Unable to find specified shader '{shaderName}'");
|
||||||
|
|
||||||
_decalShader = shader;
|
_shader = shader;
|
||||||
|
_decalMaterial = null;
|
||||||
|
_previewMaterial = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRenderQueue(int queue) {
|
public void SetRenderQueue(int queue) {
|
||||||
@ -101,10 +122,21 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMaterials() {
|
public void UpdateMaterials() {
|
||||||
|
if (_decalMaterial == null) {
|
||||||
|
_decalMaterial = DecalMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_previewMaterial == null) {
|
||||||
|
_previewMaterial = PreviewMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
UpdateMaterial(_decalMaterial);
|
UpdateMaterial(_decalMaterial);
|
||||||
|
UpdateMaterial(_previewMaterial);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMaterial(Material material) {
|
public void UpdateMaterial(Material material) {
|
||||||
|
if (material == null) throw new ArgumentNullException("material cannot be null");
|
||||||
|
|
||||||
foreach (var property in _materialProperties) {
|
foreach (var property in _materialProperties) {
|
||||||
property.Modify(material);
|
property.Modify(material);
|
||||||
}
|
}
|
||||||
|
@ -3,29 +3,54 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace ConformalDecals.MaterialModifiers {
|
namespace ConformalDecals.MaterialModifiers {
|
||||||
public class MaterialTextureProperty : MaterialProperty {
|
public class MaterialTextureProperty : MaterialProperty {
|
||||||
public Texture2D texture;
|
[SerializeField] public Texture2D texture;
|
||||||
|
|
||||||
public bool IsNormal { get; }
|
[SerializeField] public bool isNormal;
|
||||||
public bool IsMain { get; }
|
[SerializeField] public bool isMain;
|
||||||
public bool AutoScale { get; }
|
[SerializeField] public bool autoScale;
|
||||||
|
|
||||||
private readonly Rect _tileRect;
|
[SerializeField] private bool _hasTile;
|
||||||
|
[SerializeField] private Rect _tileRect;
|
||||||
|
[SerializeField] private Vector2 _textureOffset = Vector2.zero;
|
||||||
|
[SerializeField] private Vector2 _textureScale = Vector2.one;
|
||||||
|
|
||||||
public float AspectRatio => _tileRect.height / _tileRect.width;
|
public float AspectRatio {
|
||||||
|
get {
|
||||||
|
if (texture == null) return 1;
|
||||||
|
if (!_hasTile || Mathf.Approximately(0, _tileRect.width)) return ((float) texture.height) / ((float) texture.width);
|
||||||
|
return _tileRect.height / _tileRect.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Vector2 _textureOffset;
|
public Rect TileRect {
|
||||||
private readonly Vector2 _textureScale;
|
get => _tileRect;
|
||||||
|
set {
|
||||||
|
_hasTile = !(Mathf.Abs(value.width) < 0.1) || !(Mathf.Abs(value.height) < 0.1);
|
||||||
|
|
||||||
public MaterialTextureProperty(ConfigNode node) : base(node) {
|
_tileRect = value;
|
||||||
IsNormal = ParsePropertyBool(node, "isNormalMap", true, PropertyName == "_BumpMap");
|
UpdateTiling();
|
||||||
IsMain = ParsePropertyBool(node, "isMain", true);
|
}
|
||||||
AutoScale = ParsePropertyBool(node, "autoScale", true);
|
}
|
||||||
var textureUrl = node.GetValue("textureURL");
|
|
||||||
|
|
||||||
if ((textureUrl == null && IsNormal) || textureUrl == "Bump") {
|
public override void ParseNode(ConfigNode node) {
|
||||||
|
base.ParseNode(node);
|
||||||
|
|
||||||
|
isNormal = ParsePropertyBool(node, "isNormalMap", true, (Name == "_BumpMap") || isNormal);
|
||||||
|
isMain = ParsePropertyBool(node, "isMain", true, isMain);
|
||||||
|
autoScale = ParsePropertyBool(node, "autoScale", true, autoScale);
|
||||||
|
|
||||||
|
SetTexture(node.GetValue("textureUrl"));
|
||||||
|
|
||||||
|
if (node.HasValue("tileRect")) {
|
||||||
|
TileRect = ParsePropertyRect(node, "tileRect", true, _tileRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTexture(string textureUrl) {
|
||||||
|
if ((textureUrl == null && isNormal) || textureUrl == "Bump") {
|
||||||
texture = Texture2D.normalTexture;
|
texture = Texture2D.normalTexture;
|
||||||
}
|
}
|
||||||
else if ((textureUrl == null && !IsNormal) || textureUrl == "White") {
|
else if ((textureUrl == null && !isNormal) || textureUrl == "White") {
|
||||||
texture = Texture2D.whiteTexture;
|
texture = Texture2D.whiteTexture;
|
||||||
}
|
}
|
||||||
else if (textureUrl == "Black") {
|
else if (textureUrl == "Black") {
|
||||||
@ -36,48 +61,43 @@ namespace ConformalDecals.MaterialModifiers {
|
|||||||
|
|
||||||
if (textureInfo == null) throw new Exception($"Cannot find texture: '{textureUrl}'");
|
if (textureInfo == null) throw new Exception($"Cannot find texture: '{textureUrl}'");
|
||||||
|
|
||||||
texture = IsNormal ? textureInfo.normalMap : textureInfo.texture;
|
texture = isNormal ? textureInfo.normalMap : textureInfo.texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texture == null) throw new Exception($"Cannot get texture from texture info '{textureUrl}' isNormalMap = {IsNormal}");
|
if (texture == null) throw new Exception($"Cannot get texture from texture info '{textureUrl}', isNormalMap = {isNormal}");
|
||||||
|
UpdateTiling();
|
||||||
_tileRect = ParsePropertyRect(node, "tileRect", true, new Rect(0, 0, texture.width, texture.height));
|
|
||||||
|
|
||||||
_textureScale.x = _tileRect.width / texture.width;
|
|
||||||
_textureScale.y = _tileRect.height / texture.height;
|
|
||||||
|
|
||||||
_textureOffset.x = _tileRect.x / texture.width;
|
|
||||||
_textureOffset.y = _tileRect.y / texture.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MaterialTextureProperty(string name, Texture2D texture, Rect tileRect = default,
|
|
||||||
bool isNormal = false, bool isMain = false, bool autoScale = false) : base(name) {
|
|
||||||
|
|
||||||
this.texture = texture;
|
|
||||||
|
|
||||||
_tileRect = tileRect == default ? new Rect(0, 0, this.texture.width, this.texture.height) : tileRect;
|
|
||||||
|
|
||||||
IsNormal = isNormal;
|
|
||||||
IsMain = isMain;
|
|
||||||
AutoScale = autoScale;
|
|
||||||
|
|
||||||
_textureScale.x = _tileRect.width / this.texture.width;
|
|
||||||
_textureScale.y = _tileRect.height / this.texture.height;
|
|
||||||
|
|
||||||
_textureOffset.x = _tileRect.x / this.texture.width;
|
|
||||||
_textureOffset.y = _tileRect.y / this.texture.height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Modify(Material material) {
|
public override void Modify(Material material) {
|
||||||
|
if (material == null) throw new ArgumentNullException(nameof(material));
|
||||||
|
if (texture == null) {
|
||||||
|
texture = Texture2D.whiteTexture;
|
||||||
|
throw new NullReferenceException("texture is null, but should not be");
|
||||||
|
}
|
||||||
|
|
||||||
material.SetTexture(_propertyID, texture);
|
material.SetTexture(_propertyID, texture);
|
||||||
material.SetTextureOffset(_propertyID, _textureOffset);
|
material.SetTextureOffset(_propertyID, _textureOffset);
|
||||||
material.SetTextureScale(_propertyID, _textureScale);
|
material.SetTextureScale(_propertyID, _textureScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateScale(Material material, Vector2 scale) {
|
public void UpdateScale(Material material, Vector2 scale) {
|
||||||
if (AutoScale) {
|
if (autoScale) {
|
||||||
material.SetTextureScale(_propertyID, new Vector2(_textureScale.x * scale.x, _textureScale.y * scale.y));
|
material.SetTextureScale(_propertyID, new Vector2(_textureScale.x * scale.x, _textureScale.y * scale.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateTiling() {
|
||||||
|
if (_hasTile) {
|
||||||
|
_textureScale.x = Mathf.Approximately(0, _tileRect.width) ? 1 : _tileRect.width / texture.width;
|
||||||
|
_textureScale.y = Mathf.Approximately(0, _tileRect.height) ? 1 : _tileRect.height / texture.height;
|
||||||
|
|
||||||
|
_textureOffset.x = _tileRect.x / texture.width;
|
||||||
|
_textureOffset.y = _tileRect.y / texture.height;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_textureScale = Vector2.one;
|
||||||
|
_textureOffset = Vector2.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,26 +7,27 @@ using UnityEngine;
|
|||||||
namespace ConformalDecals {
|
namespace ConformalDecals {
|
||||||
public abstract class ModuleConformalDecalBase : PartModule {
|
public abstract class ModuleConformalDecalBase : PartModule {
|
||||||
[KSPField(guiName = "#LOC_ConformalDecals_gui-scale", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
|
[KSPField(guiName = "#LOC_ConformalDecals_gui-scale", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
|
||||||
UI_FloatRange(minValue = 0.05f, maxValue = 4f, stepIncrement = 0.05f)]
|
UI_FloatRange(stepIncrement = 0.05f)]
|
||||||
public float scale = 1.0f;
|
public float scale = 1.0f;
|
||||||
|
|
||||||
[KSPField(guiName = "#LOC_ConformalDecals_gui-depth", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
|
[KSPField(guiName = "#LOC_ConformalDecals_gui-depth", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2", guiUnits = "m"),
|
||||||
UI_FloatRange(minValue = 0.05f, maxValue = 4f, stepIncrement = 0.05f)]
|
UI_FloatRange(stepIncrement = 0.02f)]
|
||||||
public float depth = 1.0f;
|
public float depth = 0.2f;
|
||||||
|
|
||||||
[KSPField(guiName = "#LOC_ConformalDecals_gui-opacity", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
|
[KSPField(guiName = "#LOC_ConformalDecals_gui-opacity", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
|
||||||
UI_FloatRange(minValue = 0.0f, maxValue = 1f, stepIncrement = 0.05f)]
|
UI_FloatRange(stepIncrement = 0.05f)]
|
||||||
public float opacity = 1.0f;
|
public float opacity = 1.0f;
|
||||||
|
|
||||||
[KSPField(guiName = "#LOC_ConformalDecals_gui-cutoff", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
|
[KSPField(guiName = "#LOC_ConformalDecals_gui-cutoff", guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "P0"),
|
||||||
UI_FloatRange(minValue = 0.0f, maxValue = 1f, stepIncrement = 0.05f)]
|
UI_FloatRange(stepIncrement = 0.05f)]
|
||||||
public float cutoff = 0.5f;
|
public float cutoff = 0.5f;
|
||||||
|
|
||||||
|
[KSPField] public string shader = "ConformalDecals/Paint/Diffuse";
|
||||||
|
|
||||||
[KSPField] public string decalFront = string.Empty;
|
[KSPField] public string decalFront = string.Empty;
|
||||||
[KSPField] public string decalBack = string.Empty;
|
[KSPField] public string decalBack = string.Empty;
|
||||||
[KSPField] public string decalModel = string.Empty;
|
[KSPField] public string decalModel = string.Empty;
|
||||||
[KSPField] public string decalProjector = string.Empty;
|
[KSPField] public string decalProjector = string.Empty;
|
||||||
[KSPField] public string decalShader = "ConformalDecals/Paint/Diffuse";
|
|
||||||
|
|
||||||
[KSPField] public Transform decalFrontTransform;
|
[KSPField] public Transform decalFrontTransform;
|
||||||
[KSPField] public Transform decalBackTransform;
|
[KSPField] public Transform decalBackTransform;
|
||||||
@ -39,10 +40,9 @@ namespace ConformalDecals {
|
|||||||
[KSPField] public bool cutoffAdjustable = true;
|
[KSPField] public bool cutoffAdjustable = true;
|
||||||
|
|
||||||
[KSPField] public Vector2 scaleRange = new Vector2(0, 4);
|
[KSPField] public Vector2 scaleRange = new Vector2(0, 4);
|
||||||
[KSPField] public Vector2 depthRange = new Vector2(0, 4);
|
[KSPField] public Vector2 depthRange = new Vector2(0, 2);
|
||||||
[KSPField] public Vector2 opacityRange = new Vector2(0, 1);
|
[KSPField] public Vector2 opacityRange = new Vector2(0, 1);
|
||||||
[KSPField] public Vector2 cutoffRange = new Vector2(0, 1);
|
[KSPField] public Vector2 cutoffRange = new Vector2(0, 1);
|
||||||
[KSPField] public Vector2 decalQueueRange = new Vector2(2100, 2400);
|
|
||||||
|
|
||||||
[KSPField] public bool updateBackScale = true;
|
[KSPField] public bool updateBackScale = true;
|
||||||
[KSPField] public bool useBaseNormal = true;
|
[KSPField] public bool useBaseNormal = true;
|
||||||
@ -50,7 +50,10 @@ namespace ConformalDecals {
|
|||||||
[KSPField] public MaterialPropertyCollection materialProperties;
|
[KSPField] public MaterialPropertyCollection materialProperties;
|
||||||
|
|
||||||
[KSPField] public Material backMaterial;
|
[KSPField] public Material backMaterial;
|
||||||
|
[KSPField] public Vector2 backTextureBaseScale;
|
||||||
|
|
||||||
|
private const int DecalQueueMin = 2100;
|
||||||
|
private const int DecalQueueMax = 2400;
|
||||||
private static int _decalQueueCounter = -1;
|
private static int _decalQueueCounter = -1;
|
||||||
|
|
||||||
private List<ProjectionTarget> _targets;
|
private List<ProjectionTarget> _targets;
|
||||||
@ -58,13 +61,16 @@ namespace ConformalDecals {
|
|||||||
private bool _isAttached;
|
private bool _isAttached;
|
||||||
private Matrix4x4 _orthoMatrix;
|
private Matrix4x4 _orthoMatrix;
|
||||||
private Bounds _decalBounds;
|
private Bounds _decalBounds;
|
||||||
private Vector2 _backTextureBaseScale;
|
|
||||||
|
private Material _decalMaterial;
|
||||||
|
private Material _previewMaterial;
|
||||||
|
|
||||||
|
|
||||||
private int DecalQueue {
|
private int DecalQueue {
|
||||||
get {
|
get {
|
||||||
_decalQueueCounter++;
|
_decalQueueCounter++;
|
||||||
if (_decalQueueCounter > decalQueueRange.y || _decalQueueCounter < decalQueueRange.x) {
|
if (_decalQueueCounter > DecalQueueMax || _decalQueueCounter < DecalQueueMin) {
|
||||||
_decalQueueCounter = (int) decalQueueRange.x;
|
_decalQueueCounter = DecalQueueMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _decalQueueCounter;
|
return _decalQueueCounter;
|
||||||
@ -74,10 +80,6 @@ namespace ConformalDecals {
|
|||||||
public override void OnLoad(ConfigNode node) {
|
public override void OnLoad(ConfigNode node) {
|
||||||
this.Log("Loading module");
|
this.Log("Loading module");
|
||||||
try {
|
try {
|
||||||
if (HighLogic.LoadedSceneIsEditor) {
|
|
||||||
UpdateTweakables();
|
|
||||||
}
|
|
||||||
|
|
||||||
// find front transform
|
// find front transform
|
||||||
decalFrontTransform = part.FindModelTransform(decalFront);
|
decalFrontTransform = part.FindModelTransform(decalFront);
|
||||||
if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'.");
|
if (decalFrontTransform == null) throw new FormatException($"Could not find decalFront transform: '{decalFront}'.");
|
||||||
@ -113,31 +115,24 @@ namespace ConformalDecals {
|
|||||||
if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'.");
|
if (decalProjectorTransform == null) throw new FormatException($"Could not find decalProjector transform: '{decalProjector}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// get back material if necessary
|
|
||||||
if (updateBackScale) {
|
if (HighLogic.LoadedSceneIsEditor) {
|
||||||
this.Log("Getting material and base scale for back material");
|
UpdateTweakables();
|
||||||
var backRenderer = decalBackTransform.GetComponent<MeshRenderer>();
|
|
||||||
if (backRenderer == null) {
|
|
||||||
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
|
|
||||||
updateBackScale = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
backMaterial = backRenderer.material;
|
|
||||||
if (backMaterial == null) {
|
|
||||||
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
|
|
||||||
updateBackScale = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update EVERYTHING if currently attached
|
// setup material properties
|
||||||
if (_isAttached) {
|
if (HighLogic.LoadedSceneIsGame) {
|
||||||
UpdateScale();
|
// additional load, in flight or in the editor
|
||||||
UpdateTargets();
|
materialProperties = ScriptableObject.Instantiate(materialProperties);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
// first load, so get everything set up
|
||||||
|
materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>();
|
||||||
|
materialProperties.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// set shader
|
||||||
|
materialProperties.SetShader(shader);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
this.LogException("Exception parsing partmodule", e);
|
this.LogException("Exception parsing partmodule", e);
|
||||||
@ -147,6 +142,7 @@ namespace ConformalDecals {
|
|||||||
public override void OnStart(StartState state) {
|
public override void OnStart(StartState state) {
|
||||||
this.Log("Starting module");
|
this.Log("Starting module");
|
||||||
|
|
||||||
|
// handle tweakables
|
||||||
if (HighLogic.LoadedSceneIsEditor) {
|
if (HighLogic.LoadedSceneIsEditor) {
|
||||||
GameEvents.onEditorPartEvent.Add(OnEditorEvent);
|
GameEvents.onEditorPartEvent.Add(OnEditorEvent);
|
||||||
GameEvents.onVariantApplied.Add(OnVariantApplied);
|
GameEvents.onVariantApplied.Add(OnVariantApplied);
|
||||||
@ -154,23 +150,24 @@ namespace ConformalDecals {
|
|||||||
UpdateTweakables();
|
UpdateTweakables();
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate orthogonal projection matrix and offset it by 0.5 on x and y axes
|
// clone materialProperties and setup queue
|
||||||
_orthoMatrix = Matrix4x4.identity;
|
if (HighLogic.LoadedSceneIsGame) {
|
||||||
_orthoMatrix[0, 3] = 0.5f;
|
materialProperties = ScriptableObject.Instantiate(materialProperties);
|
||||||
_orthoMatrix[1, 3] = 0.5f;
|
|
||||||
|
|
||||||
// instantiate decal material and set uniqueish queue
|
|
||||||
materialProperties.SetRenderQueue(DecalQueue);
|
materialProperties.SetRenderQueue(DecalQueue);
|
||||||
|
|
||||||
// set initial attachment state
|
// set initial attachment state
|
||||||
if (part.parent == null) {
|
if (part.parent == null) {
|
||||||
OnDetach();
|
_isAttached = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
OnAttach();
|
OnAttach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateMaterials();
|
||||||
|
UpdateScale();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnDestroy() {
|
public void OnDestroy() {
|
||||||
GameEvents.onEditorPartEvent.Remove(OnEditorEvent);
|
GameEvents.onEditorPartEvent.Remove(OnEditorEvent);
|
||||||
GameEvents.onVariantApplied.Remove(OnVariantApplied);
|
GameEvents.onVariantApplied.Remove(OnVariantApplied);
|
||||||
@ -179,25 +176,25 @@ namespace ConformalDecals {
|
|||||||
Camera.onPreCull -= Render;
|
Camera.onPreCull -= Render;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSizeTweakEvent(BaseField field, object obj) {
|
protected void OnSizeTweakEvent(BaseField field, object obj) {
|
||||||
// scale or depth values have been changed, so update scale
|
// scale or depth values have been changed, so update scale
|
||||||
// and update projection matrices if attached
|
// and update projection matrices if attached
|
||||||
UpdateScale();
|
UpdateScale();
|
||||||
if (_isAttached) UpdateProjection();
|
if (_isAttached) UpdateProjection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMaterialTweakEvent(BaseField field, object obj) {
|
protected void OnMaterialTweakEvent(BaseField field, object obj) {
|
||||||
materialProperties.SetOpacity(opacity);
|
materialProperties.SetOpacity(opacity);
|
||||||
materialProperties.SetCutoff(cutoff);
|
materialProperties.SetCutoff(cutoff);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVariantApplied(Part eventPart, PartVariant variant) {
|
protected void OnVariantApplied(Part eventPart, PartVariant variant) {
|
||||||
if (_isAttached && eventPart == part.parent) {
|
if (_isAttached && eventPart == part.parent) {
|
||||||
UpdateTargets();
|
UpdateTargets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEditorEvent(ConstructionEventType eventType, Part eventPart) {
|
protected void OnEditorEvent(ConstructionEventType eventType, Part eventPart) {
|
||||||
if (eventPart != this.part) return;
|
if (eventPart != this.part) return;
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
case ConstructionEventType.PartAttached:
|
case ConstructionEventType.PartAttached:
|
||||||
@ -213,7 +210,7 @@ namespace ConformalDecals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAttach() {
|
protected void OnAttach() {
|
||||||
if (part.parent == null) {
|
if (part.parent == null) {
|
||||||
this.LogError("Attach function called but part has no parent!");
|
this.LogError("Attach function called but part has no parent!");
|
||||||
_isAttached = false;
|
_isAttached = false;
|
||||||
@ -224,7 +221,6 @@ namespace ConformalDecals {
|
|||||||
|
|
||||||
this.Log($"Decal attached to {part.parent.partName}");
|
this.Log($"Decal attached to {part.parent.partName}");
|
||||||
|
|
||||||
UpdateTargets();
|
|
||||||
|
|
||||||
// hide preview model
|
// hide preview model
|
||||||
decalFrontTransform.gameObject.SetActive(false);
|
decalFrontTransform.gameObject.SetActive(false);
|
||||||
@ -233,11 +229,12 @@ namespace ConformalDecals {
|
|||||||
// add to preCull delegate
|
// add to preCull delegate
|
||||||
Camera.onPreCull += Render;
|
Camera.onPreCull += Render;
|
||||||
|
|
||||||
UpdateScale();
|
//UpdateScale();
|
||||||
|
UpdateTargets();
|
||||||
UpdateProjection();
|
UpdateProjection();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDetach() {
|
protected void OnDetach() {
|
||||||
_isAttached = false;
|
_isAttached = false;
|
||||||
|
|
||||||
// unhide preview model
|
// unhide preview model
|
||||||
@ -247,13 +244,19 @@ namespace ConformalDecals {
|
|||||||
// remove from preCull delegate
|
// remove from preCull delegate
|
||||||
Camera.onPreCull -= Render;
|
Camera.onPreCull -= Render;
|
||||||
|
|
||||||
UpdateScale();
|
//UpdateScale();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateScale() {
|
protected void UpdateScale() {
|
||||||
|
var aspectRatio = materialProperties.AspectRatio;
|
||||||
|
this.Log($"Aspect ratio is {aspectRatio}");
|
||||||
var size = new Vector2(scale, scale * materialProperties.AspectRatio);
|
var size = new Vector2(scale, scale * materialProperties.AspectRatio);
|
||||||
|
|
||||||
// update orthogonal matrix scale
|
// update orthogonal matrix scale
|
||||||
|
_orthoMatrix = Matrix4x4.identity;
|
||||||
|
_orthoMatrix[0, 3] = 0.5f;
|
||||||
|
_orthoMatrix[1, 3] = 0.5f;
|
||||||
|
|
||||||
_orthoMatrix[0, 0] = 1 / size.x;
|
_orthoMatrix[0, 0] = 1 / size.x;
|
||||||
_orthoMatrix[1, 1] = 1 / size.y;
|
_orthoMatrix[1, 1] = 1 / size.y;
|
||||||
_orthoMatrix[2, 2] = 1 / depth;
|
_orthoMatrix[2, 2] = 1 / depth;
|
||||||
@ -262,28 +265,67 @@ namespace ConformalDecals {
|
|||||||
_decalBounds.center = Vector3.forward * (depth / 2);
|
_decalBounds.center = Vector3.forward * (depth / 2);
|
||||||
_decalBounds.extents = new Vector3(size.x / 2, size.y / 2, depth / 2);
|
_decalBounds.extents = new Vector3(size.x / 2, size.y / 2, depth / 2);
|
||||||
|
|
||||||
|
if (decalModelTransform == null) {
|
||||||
|
this.LogError("decalModelTransform is null!");
|
||||||
|
}
|
||||||
|
|
||||||
// rescale preview model
|
// rescale preview model
|
||||||
decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2);
|
decalModelTransform.localScale = new Vector3(size.x, size.y, (size.x + size.y) / 2);
|
||||||
|
|
||||||
// update back material scale
|
// update back material scale
|
||||||
if (updateBackScale) {
|
if (updateBackScale) {
|
||||||
backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * _backTextureBaseScale.x, size.y * _backTextureBaseScale.y));
|
backMaterial.SetTextureScale(PropertyIDs._MainTex, new Vector2(size.x * backTextureBaseScale.x, size.y * backTextureBaseScale.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update material scale
|
// update material scale
|
||||||
materialProperties.SetScale(size);
|
materialProperties.SetScale(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateProjection() {
|
protected void UpdateMaterials() {
|
||||||
|
materialProperties.UpdateMaterials();
|
||||||
|
_decalMaterial = materialProperties.DecalMaterial;
|
||||||
|
_previewMaterial = materialProperties.PreviewMaterial;
|
||||||
|
|
||||||
|
// get back material if necessary
|
||||||
|
if (updateBackScale) {
|
||||||
|
this.Log("Getting material and base scale for back material");
|
||||||
|
var backRenderer = decalBackTransform.GetComponent<MeshRenderer>();
|
||||||
|
if (backRenderer == null) {
|
||||||
|
this.LogError($"Specified decalBack transform {decalBack} has no renderer attached! Setting updateBackScale to false.");
|
||||||
|
updateBackScale = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
backMaterial = backRenderer.material;
|
||||||
|
if (backMaterial == null) {
|
||||||
|
this.LogError($"Specified decalBack transform {decalBack} has a renderer but no material! Setting updateBackScale to false.");
|
||||||
|
updateBackScale = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (backTextureBaseScale == default) backTextureBaseScale = backMaterial.GetTextureScale(PropertyIDs._MainTex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decalFrontTransform == null) {
|
||||||
|
this.LogError("No reference to decal front transform");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decalFrontTransform.GetComponent<MeshRenderer>().material = _previewMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateProjection() {
|
||||||
if (!_isAttached) return;
|
if (!_isAttached) return;
|
||||||
|
|
||||||
|
var bounds = new OrientedBounds(decalProjectorTransform.localToWorldMatrix, _decalBounds);
|
||||||
|
|
||||||
// project to each target object
|
// project to each target object
|
||||||
foreach (var target in _targets) {
|
foreach (var target in _targets) {
|
||||||
target.Project(_orthoMatrix, new OrientedBounds(decalProjectorTransform.localToWorldMatrix, _decalBounds), decalProjectorTransform);
|
target.Project(_orthoMatrix, bounds, decalProjectorTransform, useBaseNormal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTargets() {
|
protected void UpdateTargets() {
|
||||||
if (_targets == null) {
|
if (_targets == null) {
|
||||||
_targets = new List<ProjectionTarget>();
|
_targets = new List<ProjectionTarget>();
|
||||||
}
|
}
|
||||||
@ -319,7 +361,7 @@ namespace ConformalDecals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTweakables() {
|
protected void UpdateTweakables() {
|
||||||
// setup tweakable fields
|
// setup tweakable fields
|
||||||
var scaleField = Fields[nameof(scale)];
|
var scaleField = Fields[nameof(scale)];
|
||||||
var depthField = Fields[nameof(depth)];
|
var depthField = Fields[nameof(depth)];
|
||||||
@ -369,12 +411,12 @@ namespace ConformalDecals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render(Camera camera) {
|
protected void Render(Camera camera) {
|
||||||
if (!_isAttached) return;
|
if (!_isAttached) return;
|
||||||
|
|
||||||
// render on each target object
|
// render on each target object
|
||||||
foreach (var target in _targets) {
|
foreach (var target in _targets) {
|
||||||
target.Render(materialProperties.DecalMaterial, part.mpb, camera);
|
target.Render(_decalMaterial, part.mpb, camera);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using ConformalDecals.MaterialModifiers;
|
using ConformalDecals.MaterialModifiers;
|
||||||
using ConformalDecals.Util;
|
using ConformalDecals.Util;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -7,30 +6,36 @@ namespace ConformalDecals {
|
|||||||
public class ModuleConformalDecalFlag : ModuleConformalDecalBase {
|
public class ModuleConformalDecalFlag : ModuleConformalDecalBase {
|
||||||
[KSPField] public MaterialTextureProperty flagTextureProperty;
|
[KSPField] public MaterialTextureProperty flagTextureProperty;
|
||||||
|
|
||||||
|
private const string defaultFlag = "Squad/Flags/default";
|
||||||
|
|
||||||
public override void OnLoad(ConfigNode node) {
|
public override void OnLoad(ConfigNode node) {
|
||||||
|
|
||||||
if (materialProperties == null) {
|
|
||||||
// materialProperties is null, so make a new one
|
|
||||||
materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>();
|
|
||||||
materialProperties.Initialize();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// materialProperties already exists, so make a copy
|
|
||||||
materialProperties = ScriptableObject.Instantiate(materialProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set shader
|
|
||||||
materialProperties.SetShader(decalShader);
|
|
||||||
|
|
||||||
base.OnLoad(node);
|
base.OnLoad(node);
|
||||||
|
|
||||||
|
|
||||||
|
if (HighLogic.LoadedSceneIsGame) {
|
||||||
|
UpdateMaterials();
|
||||||
|
UpdateScale();
|
||||||
|
UpdateProjection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnStart(StartState state) {
|
public override void OnStart(StartState state) {
|
||||||
base.OnStart(state);
|
base.OnStart(state);
|
||||||
|
|
||||||
|
if (HighLogic.LoadedSceneIsGame) {
|
||||||
UpdateFlag(EditorLogic.FlagURL != string.Empty ? EditorLogic.FlagURL : HighLogic.CurrentGame.flagURL);
|
UpdateFlag(EditorLogic.FlagURL != string.Empty ? EditorLogic.FlagURL : HighLogic.CurrentGame.flagURL);
|
||||||
GameEvents.onMissionFlagSelect.Add(UpdateFlag);
|
GameEvents.onMissionFlagSelect.Add(UpdateFlag);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
UpdateFlag(defaultFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnIconCreate() {
|
||||||
|
this.Log("called OnIconCreate");
|
||||||
|
OnStart(StartState.None);
|
||||||
|
UpdateScale();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateFlag(string flagUrl) {
|
private void UpdateFlag(string flagUrl) {
|
||||||
this.Log($"Loading flag texture '{flagUrl}'.");
|
this.Log($"Loading flag texture '{flagUrl}'.");
|
||||||
@ -42,14 +47,17 @@ namespace ConformalDecals {
|
|||||||
|
|
||||||
if (flagTextureProperty == null) {
|
if (flagTextureProperty == null) {
|
||||||
this.Log("Initializing flag property");
|
this.Log("Initializing flag property");
|
||||||
flagTextureProperty = new MaterialTextureProperty("_Decal", flagTexture, isMain: true);
|
flagTextureProperty = ScriptableObject.CreateInstance<MaterialTextureProperty>();
|
||||||
|
flagTextureProperty.Name = "_Decal";
|
||||||
|
flagTextureProperty.isMain = true;
|
||||||
materialProperties.AddProperty(flagTextureProperty);
|
materialProperties.AddProperty(flagTextureProperty);
|
||||||
}
|
}
|
||||||
else {
|
else { }
|
||||||
flagTextureProperty.texture = flagTexture;
|
|
||||||
}
|
|
||||||
|
|
||||||
materialProperties.UpdateMaterials();
|
flagTextureProperty.texture = flagTexture;
|
||||||
|
|
||||||
|
|
||||||
|
UpdateMaterials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,35 +5,42 @@ using UnityEngine;
|
|||||||
namespace ConformalDecals {
|
namespace ConformalDecals {
|
||||||
public class ModuleConformalDecalGeneric : ModuleConformalDecalBase {
|
public class ModuleConformalDecalGeneric : ModuleConformalDecalBase {
|
||||||
public override void OnLoad(ConfigNode node) {
|
public override void OnLoad(ConfigNode node) {
|
||||||
|
base.OnLoad(node);
|
||||||
if (materialProperties == null) {
|
|
||||||
// materialProperties is null, so make a new one
|
|
||||||
materialProperties = ScriptableObject.CreateInstance<MaterialPropertyCollection>();
|
|
||||||
materialProperties.Initialize();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// materialProperties already exists, so make a copy
|
|
||||||
materialProperties = ScriptableObject.Instantiate(materialProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set shader
|
// set shader
|
||||||
materialProperties.SetShader(decalShader);
|
materialProperties.SetShader(shader);
|
||||||
// add texture nodes
|
// add texture nodes
|
||||||
foreach (var textureNode in node.GetNodes("TEXTURE")) {
|
foreach (var textureNode in node.GetNodes("TEXTURE")) {
|
||||||
materialProperties.AddProperty(new MaterialTextureProperty(textureNode));
|
var textureProperty = ScriptableObject.CreateInstance<MaterialTextureProperty>();
|
||||||
|
textureProperty.ParseNode(textureNode);
|
||||||
|
materialProperties.AddProperty(textureProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add float nodes
|
// add float nodes
|
||||||
foreach (var floatNode in node.GetNodes("FLOAT")) {
|
foreach (var floatNode in node.GetNodes("FLOAT")) {
|
||||||
materialProperties.AddProperty(new MaterialFloatProperty(floatNode));
|
var floatProperty = ScriptableObject.CreateInstance<MaterialFloatProperty>();
|
||||||
|
floatProperty.ParseNode(floatNode);
|
||||||
|
materialProperties.AddProperty(floatProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add color nodes
|
// add color nodes
|
||||||
foreach (var colorNode in node.GetNodes("COLOR")) {
|
foreach (var colorNode in node.GetNodes("COLOR")) {
|
||||||
materialProperties.AddProperty(new MaterialColorProperty(colorNode));
|
var colorProperty = ScriptableObject.CreateInstance<MaterialColorProperty>();
|
||||||
|
colorProperty.ParseNode(colorNode);
|
||||||
|
materialProperties.AddProperty(colorProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnLoad(node);
|
if (HighLogic.LoadedSceneIsGame) {
|
||||||
|
UpdateMaterials();
|
||||||
|
UpdateScale();
|
||||||
|
UpdateProjection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnIconCreate() {
|
||||||
|
this.Log("called OnIconCreate");
|
||||||
|
OnStart(StartState.None);
|
||||||
|
UpdateScale();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,9 +25,25 @@ namespace ConformalDecals {
|
|||||||
target = targetRenderer.transform;
|
target = targetRenderer.transform;
|
||||||
_targetRenderer = targetRenderer;
|
_targetRenderer = targetRenderer;
|
||||||
_targetMesh = targetMesh;
|
_targetMesh = targetMesh;
|
||||||
var targetMaterial = targetRenderer.sharedMaterial;
|
|
||||||
|
|
||||||
_decalMPB = new MaterialPropertyBlock();
|
_decalMPB = new MaterialPropertyBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Project(Matrix4x4 orthoMatrix, OrientedBounds projectorBounds, Transform projector, bool useBaseNormal) {
|
||||||
|
var targetBounds = _targetRenderer.bounds;
|
||||||
|
if (projectorBounds.Intersects(targetBounds)) {
|
||||||
|
_projectionEnabled = true;
|
||||||
|
|
||||||
|
var targetMaterial = _targetRenderer.sharedMaterial;
|
||||||
|
var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix;
|
||||||
|
|
||||||
|
var projectionMatrix = orthoMatrix * projectorToTargetMatrix.inverse;
|
||||||
|
var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized;
|
||||||
|
var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized;
|
||||||
|
|
||||||
|
_decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix);
|
||||||
|
_decalMPB.SetVector(_decalNormalID, decalNormal);
|
||||||
|
_decalMPB.SetVector(_decalTangentID, decalTangent);
|
||||||
|
Debug.Log($"Projection enabled for {target.gameObject}");
|
||||||
|
|
||||||
if (useBaseNormal && targetMaterial.HasProperty(normalID)) {
|
if (useBaseNormal && targetMaterial.HasProperty(normalID)) {
|
||||||
var normal = targetMaterial.GetTexture(normalID);
|
var normal = targetMaterial.GetTexture(normalID);
|
||||||
@ -42,22 +58,6 @@ namespace ConformalDecals {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Project(Matrix4x4 orthoMatrix, OrientedBounds projectorBounds, Transform projector) {
|
|
||||||
var targetBounds = _targetRenderer.bounds;
|
|
||||||
if (projectorBounds.Intersects(targetBounds)) {
|
|
||||||
_projectionEnabled = true;
|
|
||||||
var projectorToTargetMatrix = target.worldToLocalMatrix * projector.localToWorldMatrix;
|
|
||||||
|
|
||||||
var projectionMatrix = orthoMatrix * projectorToTargetMatrix.inverse;
|
|
||||||
var decalNormal = projectorToTargetMatrix.MultiplyVector(Vector3.back).normalized;
|
|
||||||
var decalTangent = projectorToTargetMatrix.MultiplyVector(Vector3.right).normalized;
|
|
||||||
|
|
||||||
_decalMPB.SetMatrix(_projectionMatrixID, projectionMatrix);
|
|
||||||
_decalMPB.SetVector(_decalNormalID, decalNormal);
|
|
||||||
_decalMPB.SetVector(_decalTangentID, decalTangent);
|
|
||||||
Debug.Log($"Projection enabled for {target.gameObject}");
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
_projectionEnabled = false;
|
_projectionEnabled = false;
|
||||||
Debug.Log($"Projection disabled for {target.gameObject}");
|
Debug.Log($"Projection disabled for {target.gameObject}");
|
||||||
|
Loading…
Reference in New Issue
Block a user