From cb98e78a3feabe092b74279859f0bccc17d97d49 Mon Sep 17 00:00:00 2001 From: Andrew Cassidy Date: Tue, 30 Jul 2024 22:40:23 -0700 Subject: [PATCH] New LightingKSP to solve some issues with Deferred Deferred moves the ambient calculation to the lighting pass for very good reasons, but Unity shaders still try to do it in the base pass. Easy solution is make the GI function a no-op in deferred mode. --- .../Shaders/Decal/DecalsCommon.cginc | 120 ++++++++----- .../Decal/StandardDecal.shader.template | 2 +- .../ConformalDecals/Shaders/DecalBack.shader | 4 +- .../ConformalDecals/Shaders/LightingKSP.cginc | 161 ++++++++++++++++++ .../Shaders/LightingKSPDeferred.cginc | 90 ---------- 5 files changed, 238 insertions(+), 139 deletions(-) create mode 100644 Assets/ConformalDecals/Shaders/LightingKSP.cginc delete mode 100644 Assets/ConformalDecals/Shaders/LightingKSPDeferred.cginc diff --git a/Assets/ConformalDecals/Shaders/Decal/DecalsCommon.cginc b/Assets/ConformalDecals/Shaders/Decal/DecalsCommon.cginc index 338ed46..3c0ffc0 100644 --- a/Assets/ConformalDecals/Shaders/Decal/DecalsCommon.cginc +++ b/Assets/ConformalDecals/Shaders/Decal/DecalsCommon.cginc @@ -3,7 +3,7 @@ #include "AutoLight.cginc" #include "Lighting.cginc" -#include "../LightingKSPDeferred.cginc" +#include "../LightingKSP.cginc" #define CLIP_MARGIN 0.05 #define EDGE_MARGIN 0.01 @@ -115,6 +115,10 @@ struct v2f #ifdef UNITY_PASS_FORWARDADD UNITY_LIGHTING_COORDS(5,6) #endif //UNITY_PASS_FORWARDADD + + #if UNITY_SHOULD_SAMPLE_SH + half3 sh : TEXCOORD7; // SH + #endif }; @@ -212,14 +216,14 @@ v2f vert(appdata_decal v) } -SurfaceOutput frag_common(v2f IN, out float3 viewDir, out UnityGI gi) { +SurfaceOutput frag_common(v2f IN, out float3 worldPos, out float3 worldViewDir, out float3 viewDir) { SurfaceOutput o; // setup world-space TBN vectors UNITY_EXTRACT_TBN(IN); - float3 worldPos = float3(IN.tSpace0.w, IN.tSpace1.w, IN.tSpace2.w); - float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); + worldPos = float3(IN.tSpace0.w, IN.tSpace1.w, IN.tSpace2.w); + worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); viewDir = _unity_tbn_0 * worldViewDir.x + _unity_tbn_1 * worldViewDir.y + _unity_tbn_2 * worldViewDir.z; #ifdef DECAL_PREVIEW @@ -290,31 +294,36 @@ SurfaceOutput frag_common(v2f IN, out float3 viewDir, out UnityGI gi) { worldN = normalize(worldN); o.Normal = worldN; + return o; +} + +fixed4 frag_forward(v2f IN) : SV_Target +{ + fixed4 c = 0; + + float3 worldPos; + float3 worldViewDir; + float3 viewDir; + SurfaceOutput o = frag_common(IN, worldPos, worldViewDir, viewDir); + // compute lighting & shadowing factor - #if UNITY_PASS_DEFERRED - fixed atten = 0; - #else UNITY_LIGHT_ATTENUATION(atten, IN, worldPos) - #endif - - // setup GI - UNITY_INITIALIZE_OUTPUT(UnityGI, gi); - gi.indirect.diffuse = 0; - gi.indirect.specular = 0; - gi.light.color = 0; - gi.light.dir = half3(0,1,0); - // setup light information in forward modes - #ifndef UNITY_PASS_DEFERRED #ifndef USING_DIRECTIONAL_LIGHT fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos)); #else fixed3 lightDir = _WorldSpaceLightPos0.xyz; #endif + + // Setup lighting environment + UnityGI gi; + UNITY_INITIALIZE_OUTPUT(UnityGI, gi); + gi.indirect.diffuse = 0; + gi.indirect.specular = 0; gi.light.color = _LightColor0.rgb; gi.light.dir = lightDir; - #endif + #ifdef UNITY_PASS_FORWARDBASE // Call GI (lightmaps/SH/reflections) lighting function UnityGIInput giInput; UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput); @@ -322,32 +331,24 @@ SurfaceOutput frag_common(v2f IN, out float3 viewDir, out UnityGI gi) { giInput.worldPos = worldPos; giInput.worldViewDir = worldViewDir; giInput.atten = atten; - giInput.ambient = 0.0; - - LightingBlinnPhongSmooth_GI(o, giInput, gi); - - #ifdef DECAL_PREVIEW - if (any(IN.uv_decal > 1) || any(IN.uv_decal < 0)) o.Alpha = 0; - - o.Albedo = lerp(_Background.rgb, o.Albedo, o.Alpha) * _Color.rgb; - o.Normal = lerp(float3(0,0,1), o.Normal, o.Alpha); - o.Specular = lerp(_Background.a, o.Specular, o.Alpha); - o.Emission = lerp(0, o.Emission, o.Alpha); - o.Alpha = _Opacity; - #endif //DECAL_PREVIEW - return o; -} + #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL + giInput.ambient = IN.sh; + #else + giInput.ambient.rgb = 0.0; + #endif -fixed4 frag_forward(v2f IN) : SV_Target -{ - fixed3 viewDir = 0; - UnityGI gi; + LightingBlinnPhongKSP_GI(o, giInput, gi); + #endif - SurfaceOutput o = frag_common(IN, viewDir, gi); + #ifdef UNITY_PASS_FORWARDADD + gi.light.color *= atten; + #endif //call modified KSP lighting function - return LightingBlinnPhongSmooth(o, viewDir, gi); + c += LightingBlinnPhongKSP(o, viewDir, gi); + c.rgb += o.Emission; + return c; } void frag_deferred (v2f IN, @@ -362,15 +363,41 @@ void frag_deferred (v2f IN, half4 outGBuffer2 = 0; // define dummy normal buffer when we're not writing to it #endif + float3 worldPos; + float3 worldViewDir; float3 viewDir; + SurfaceOutput o = frag_common(IN, worldPos, worldViewDir, viewDir); + + // Setup lighting environment UnityGI gi; - SurfaceOutput o = frag_common(IN, viewDir, gi); + UNITY_INITIALIZE_OUTPUT(UnityGI, gi); + gi.indirect.diffuse = 0; + gi.indirect.specular = 0; + gi.light.color = 0; + gi.light.dir = half3(0,1,0); - #ifdef DECAL_PREVIEW - o.Alpha = 1; + UnityGIInput giInput; + UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput); + giInput.light = gi.light; + giInput.worldPos = worldPos; + giInput.worldViewDir = worldViewDir; + giInput.atten = 1; + giInput.lightmapUV = 0.0; + + #if UNITY_SHOULD_SAMPLE_SH && !UNITY_SAMPLE_FULL_SH_PER_PIXEL + giInput.ambient = 0.0 * IN.sh; + #else + giInput.ambient.rgb = 0.0; #endif - outEmission = LightingBlinnPhongSmooth_Deferred(o, viewDir, gi, outGBuffer0, outGBuffer1, outGBuffer2); + giInput.probeHDR[0] = unity_SpecCube0_HDR; + giInput.probeHDR[1] = unity_SpecCube1_HDR; + + LightingBlinnPhongKSP_GI(o, giInput, gi); + + outEmission = LightingBlinnPhongKSP_Deferred(o, worldViewDir, gi, outGBuffer0, outGBuffer1, outGBuffer2); + + // outGBuffer0 = outEmission; #ifndef UNITY_HDR_ON outEmission.rgb = exp2(-outEmission.rgb); @@ -380,13 +407,14 @@ void frag_deferred (v2f IN, outGBuffer1 *= o.Alpha; outGBuffer2.a = o.Alpha; outEmission.a = o.Alpha; + } void frag_deferred_prepass(v2f IN, out half4 outGBuffer1: SV_Target1) { + float3 worldPos; + float3 worldViewDir; float3 viewDir; - UnityGI gi; - - SurfaceOutput o = frag_common(IN, viewDir, gi); + SurfaceOutput o = frag_common(IN, worldPos, worldViewDir, viewDir); outGBuffer1 = o.Alpha; } diff --git a/Assets/ConformalDecals/Shaders/Decal/StandardDecal.shader.template b/Assets/ConformalDecals/Shaders/Decal/StandardDecal.shader.template index 169326b..a9d34c5 100644 --- a/Assets/ConformalDecals/Shaders/Decal/StandardDecal.shader.template +++ b/Assets/ConformalDecals/Shaders/Decal/StandardDecal.shader.template @@ -26,7 +26,7 @@ {% endblock %} {% block body %} - #pragma multi_compile_local __ DECAL_BASE_NORMAL + #pragma multi_compile_local __ DECAL_BASE_NORMAL DECAL_BUMPMAP #pragma multi_compile_local __ DECAL_SPECMAP #pragma multi_compile_local __ DECAL_EMISSIVE #pragma multi_compile_local __ DECAL_SDF_ALPHA diff --git a/Assets/ConformalDecals/Shaders/DecalBack.shader b/Assets/ConformalDecals/Shaders/DecalBack.shader index 07936f7..decdb74 100644 --- a/Assets/ConformalDecals/Shaders/DecalBack.shader +++ b/Assets/ConformalDecals/Shaders/DecalBack.shader @@ -28,8 +28,8 @@ Shader "ConformalDecals/Decal Back" CGPROGRAM - #include "LightingKSPDeferred.cginc" - #pragma surface surf BlinnPhongSmooth vertex:vert + #include "LightingKSP.cginc" + #pragma surface surf BlinnPhongKSP vertex:vert #pragma target 3.0 sampler2D _MainTex; diff --git a/Assets/ConformalDecals/Shaders/LightingKSP.cginc b/Assets/ConformalDecals/Shaders/LightingKSP.cginc new file mode 100644 index 0000000..94903ff --- /dev/null +++ b/Assets/ConformalDecals/Shaders/LightingKSP.cginc @@ -0,0 +1,161 @@ +// WHAT IS THIS FILE? +// this file provides a replacement for the LightingKSP.cginc file that ships with part tools for writing custom shaders. +// This version enables support for the Deferred mod +// +// HOW DO I USE IT? +// Step 1) +// replace LightingKSP.cginc in your shader folder with this file, if present. If you aren't using LightingKSP.cginc +// in your shader, add the following below `CGPROGRAM`: +// `#include "../LightingKSP.cginc"` +// +// Step 2) +// add the following above `CGPROGRAM`: +// ``` +// Stencil +// { +// Ref 1 +// Comp Always +// Pass Replace +// } +// ``` +// +// Step 3) +// there should be a line in your shader that looks like this: +// `#pragma surface surf BlinnPhongSmooth keepalpha` +// Remove the `keepalpha` if it's there. the part after `surf` is the name of the lighting function your shader uses now. +// If the lighting function is `BlinnPhong` or `BlinnPhongSmooth`, change it to `BlinnPhongKSP` +// If the lighting function is `Standard`, change it to `StandardKSP` +// If the lighting function is `StandardSpecular`, change it to `StandardSpecularKSP` + +#ifndef LIGHTING_KSP_INCLUDED +#define LIGHTING_KSP_INCLUDED + +#include "UnityPBSLighting.cginc" + +#define blinnPhongShininessPower 0.215 + +// An exact conversion from blinn-phong to PBR is impossible, but the look can be approximated perceptually +// and by observing how blinn-phong looks and feels at various settings, although it can never be perfect +// 1) The specularColor can be used as is in the PBR specular flow, just needs to be divided by PI so it sums up to 1 over the hemisphere +// 2) Blinn-phong shininess doesn't stop feeling shiny unless at very low values, like below 0.04 +// while the PBR smoothness feels more linear -> map shininess to smoothness accordingly using a function +// that increases very quickly at first then slows down, I went with something like x^(1/4) or x^(1/6) then made the power configurable +// I tried various mappings from the literature but nothing really worked as well as this +// 3) Finally I noticed that some parts still looked very shiny like the AV-R8 winglet while in stock they looked rough thanks a low +// specularColor but high shininess and specularMap, so I multiplied the smoothness by the sqrt of the specularColor and that caps +// the smoothness when specularColor is low +void GetStandardSpecularPropertiesFromLegacy(float legacyShininess, float specularMap, out float3 specular, + out float smoothness) +{ + float3 legacySpecularColor = saturate(_SpecColor); + + smoothness = pow(legacyShininess, blinnPhongShininessPower) * specularMap; + smoothness *= sqrt(length(legacySpecularColor)); + + specular = legacySpecularColor * UNITY_INV_PI; +} + +float4 _Color; + +// LEGACY BLINN-PHONG LIGHTING FUNCTION FOR KSP WITH PBR CONVERSION FOR DEFERRED + +inline float4 LightingBlinnPhongKSP(SurfaceOutput s, half3 viewDir, UnityGI gi) +{ + return LightingBlinnPhong(s,viewDir, gi); +} + +inline float4 LightingBlinnPhongKSP_Deferred(SurfaceOutput s, float3 worldViewDir, UnityGI gi, + out float4 outDiffuseOcclusion, out float4 outSpecSmoothness, + out float4 outNormal) +{ + SurfaceOutputStandardSpecular ss; + ss.Albedo = s.Albedo; + ss.Normal = s.Normal; + ss.Emission = s.Emission; + ss.Occlusion = 1; + ss.Alpha = saturate(s.Alpha); + GetStandardSpecularPropertiesFromLegacy(s.Specular, s.Gloss, ss.Specular, ss.Smoothness); + + return LightingStandardSpecular_Deferred(ss, worldViewDir, gi, outDiffuseOcclusion, outSpecSmoothness, outNormal); +} + +inline void LightingBlinnPhongKSP_GI(inout SurfaceOutput s, UnityGIInput gi_input, inout UnityGI gi) +{ + #ifndef UNITY_PASS_DEFERRED + gi = UnityGlobalIllumination(gi_input, 1.0, s.Normal); + #endif +} + +// STANDARD UNITY LIGHTING FUNCTION FOR KSP + +inline float3 LightingStandardKSP(SurfaceOutputStandard s, float3 worldViewDir, UnityGI gi) +{ + return LightingStandard(s, worldViewDir, gi); // no change +} + +inline float4 LightingStandardKSP_Deferred(SurfaceOutputStandard s, float3 worldViewDir, UnityGI gi, + out float4 outDiffuseOcclusion, + out float4 outSpecSmoothness, out float4 outNormal) +{ + return LightingStandard_Deferred(s, worldViewDir, gi, outDiffuseOcclusion, outSpecSmoothness, outNormal); +} + +inline void LightingStandardKSP_GI(inout SurfaceOutputStandard s, UnityGIInput gi_input, inout UnityGI gi) +{ + #ifndef UNITY_PASS_DEFERRED + LightingStandard_GI(s, gi_input, gi); + #endif +} + +// STANDARD SPECULAR UNITY LIGHTING FUNCTION FOR KSP + +inline float3 LightingStandardSpecularKSP(SurfaceOutputStandardSpecular s, float3 worldViewDir, UnityGI gi) +{ + return LightingStandardSpecular(s, worldViewDir, gi); // no change +} + +inline float4 LightingStandardSpecularKSP_Deferred(SurfaceOutputStandardSpecular s, float3 worldViewDir, UnityGI gi, + out float4 outDiffuseOcclusion, + out float4 outSpecSmoothness, out float4 outNormal) +{ + return LightingStandardSpecular_Deferred(s, worldViewDir, gi, outDiffuseOcclusion, outSpecSmoothness, outNormal); +} + +inline void LightingStandardSpecularKSP_GI(inout SurfaceOutputStandardSpecular s, UnityGIInput gi_input, + inout UnityGI gi) +{ + #ifndef UNITY_PASS_DEFERRED + LightingStandardSpecular_GI(s, gi_input, gi); + #endif +} + +float4 _LocalCameraPos; +float4 _LocalCameraDir; +float4 _UnderwaterFogColor; +float _UnderwaterMinAlphaFogDistance; +float _UnderwaterMaxAlbedoFog; +float _UnderwaterMaxAlphaFog; +float _UnderwaterAlbedoDistanceScalar; +float _UnderwaterAlphaDistanceScalar; +float _UnderwaterFogFactor; + +float4 UnderwaterFog(float3 worldPos, float3 color) +{ + // skip fog in deferred mode + #ifdef UNITY_PASS_DEFERRED + return float4(color, 1); + #endif + + float3 toPixel = worldPos - _LocalCameraPos.xyz; + float toPixelLength = length(toPixel); ///< Comment out the math--looks better without it. + + float underwaterDetection = _UnderwaterFogFactor * _LocalCameraDir.w; ///< sign(1 - sign(_LocalCameraPos.w)); + float albedoLerpValue = underwaterDetection * (_UnderwaterMaxAlbedoFog * saturate( + toPixelLength * _UnderwaterAlbedoDistanceScalar)); + float alphaFactor = 1 - underwaterDetection * (_UnderwaterMaxAlphaFog * saturate( + (toPixelLength - _UnderwaterMinAlphaFogDistance) * _UnderwaterAlphaDistanceScalar)); + + return float4(lerp(color, _UnderwaterFogColor.rgb, albedoLerpValue), alphaFactor); +} + +#endif diff --git a/Assets/ConformalDecals/Shaders/LightingKSPDeferred.cginc b/Assets/ConformalDecals/Shaders/LightingKSPDeferred.cginc deleted file mode 100644 index fb4d41f..0000000 --- a/Assets/ConformalDecals/Shaders/LightingKSPDeferred.cginc +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef LIGHTING_KSP_INCLUDED -#define LIGHTING_KSP_INCLUDED - -#include "UnityPBSLighting.cginc" - - -#define blinnPhongShininessPower 0.215 - -// An exact conversion from blinn-phong to PBR is impossible, but the look can be approximated perceptually -// and by observing how blinn-phong looks and feels at various settings, although it can never be perfect -// 1) The specularColor can be used as is in the PBR specular flow, just needs to be divided by PI so it sums up to 1 over the hemisphere -// 2) Blinn-phong shininess doesn't stop feeling shiny unless at very low values, like below 0.04 -// while the PBR smoothness feels more linear -> map shininess to smoothness accordingly using a function -// that increases very quickly at first then slows down, I went with something like x^(1/4) or x^(1/6) then made the power configurable -// I tried various mappings from the literature but nothing really worked as well as this -// 3) Finally I noticed that some parts still looked very shiny like the AV-R8 winglet while in stock they looked rough thanks a low -// specularColor but high shininess and specularMap, so I multiplied the smoothness by the sqrt of the specularColor and that caps -// the smoothness when specularColor is low -void GetStandardSpecularPropertiesFromLegacy(float legacyShininess, float specularMap, out float3 specular, out float smoothness) -{ - float3 legacySpecularColor = saturate(_SpecColor); - - smoothness = pow(legacyShininess, blinnPhongShininessPower) * specularMap; - smoothness *= sqrt(length(legacySpecularColor)); - - specular = legacySpecularColor * UNITY_INV_PI; -} - -float4 _Color; - -fixed4 LightingBlinnPhongSmooth(SurfaceOutput s, half3 viewDir, UnityGI gi) -{ - fixed4 c = LightingBlinnPhong(s, viewDir, gi); - // #ifdef UNITY_PASS_FORWARDADD - // c.rgb *= c.a; - // #endif - return c; -} - -half4 LightingBlinnPhongSmooth_Deferred(SurfaceOutput s, half3 viewDir, UnityGI gi, - out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, - out half4 outNormal) -{ - SurfaceOutputStandardSpecular ss; - ss.Albedo = s.Albedo; - ss.Normal = s.Normal; - ss.Emission = s.Emission; - ss.Occlusion = 1; - ss.Alpha = saturate(s.Alpha); - GetStandardSpecularPropertiesFromLegacy(s.Specular, s.Gloss, ss.Specular, ss.Smoothness); - - return LightingStandardSpecular_Deferred(ss, viewDir, gi, outDiffuseOcclusion, outSpecSmoothness, outNormal); -} - - -inline void LightingBlinnPhongSmooth_GI(inout SurfaceOutput s, UnityGIInput gi_input, inout UnityGI gi) -{ - gi = UnityGlobalIllumination(gi_input, 1.0, s.Normal); -} - -float4 _LocalCameraPos; -float4 _LocalCameraDir; -float4 _UnderwaterFogColor; -float _UnderwaterMinAlphaFogDistance; -float _UnderwaterMaxAlbedoFog; -float _UnderwaterMaxAlphaFog; -float _UnderwaterAlbedoDistanceScalar; -float _UnderwaterAlphaDistanceScalar; -float _UnderwaterFogFactor; - -float4 UnderwaterFog(float3 worldPos, float3 color) -{ - // skip fog in deferred mode - #ifdef UNITY_PASS_DEFERRED - return float4(color, 1); - #endif - - float3 toPixel = worldPos - _LocalCameraPos.xyz; - float toPixelLength = length(toPixel); ///< Comment out the math--looks better without it. - - float underwaterDetection = _UnderwaterFogFactor * _LocalCameraDir.w; ///< sign(1 - sign(_LocalCameraPos.w)); - float albedoLerpValue = underwaterDetection * (_UnderwaterMaxAlbedoFog * saturate( - toPixelLength * _UnderwaterAlbedoDistanceScalar)); - float alphaFactor = 1 - underwaterDetection * (_UnderwaterMaxAlphaFog * saturate( - (toPixelLength - _UnderwaterMinAlphaFogDistance) * _UnderwaterAlphaDistanceScalar)); - - return float4(lerp(color, _UnderwaterFogColor.rgb, albedoLerpValue), alphaFactor); -} - -#endif