Skip to content

Commit

Permalink
New LightingKSP to solve some issues with Deferred
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
drewcassidy committed Jul 31, 2024
1 parent 9469752 commit cb98e78
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 139 deletions.
120 changes: 74 additions & 46 deletions Assets/ConformalDecals/Shaders/Decal/DecalsCommon.cginc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
};


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -290,64 +294,61 @@ 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);
giInput.light = gi.light;
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,
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Assets/ConformalDecals/Shaders/DecalBack.shader
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
161 changes: 161 additions & 0 deletions Assets/ConformalDecals/Shaders/LightingKSP.cginc
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit cb98e78

Please sign in to comment.