Skip to content

Commit a391a5b

Browse files
committed
Fixed shadow acne issues that were apparent in Metal.
1 parent 4e6e9cc commit a391a5b

5 files changed

Lines changed: 71 additions & 100 deletions

File tree

Assets/LowEndMobilePipeline/Editor/LowendPipelineAssetInspector.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ internal class Styles
2929
"Offset shadow near plane to account for large triangles being distorted by pancaking");
3030

3131
public static GUIContent shadowDistante = new GUIContent("Shadow Distance", "Max shadow drawing distance");
32-
public static GUIContent shadowBias = new GUIContent("Shadow Bias");
32+
33+
public static GUIContent shadowMinBias = new GUIContent("Shadow Min Normal Bias Offset", "Minimum value of normal bias offset applied");
34+
public static GUIContent shadowBias = new GUIContent("Shadow Normal Bias", "Normal bias offset value.");
3335

3436
public static GUIContent shadowAtlasResolution = new GUIContent("Shadow Map Resolution",
3537
"Resolution of shadow map texture. If cascades are enabled all cascades will be packed into this texture resolution.");
@@ -47,7 +49,8 @@ internal class Styles
4749
private SerializedProperty m_EnableAmbientProbeProp;
4850
private SerializedProperty m_ShadowTypeProp;
4951
private SerializedProperty m_ShadowNearPlaneOffsetProp;
50-
private SerializedProperty m_ShadowBiasProperty;
52+
private SerializedProperty m_ShadowMinNormalBiasProperty;
53+
private SerializedProperty m_ShadowNormalBiasProperty;
5154
private SerializedProperty m_ShadowDistanceProp;
5255
private SerializedProperty m_ShadowAtlasResolutionProp;
5356
private SerializedProperty m_ShadowCascadesProp;
@@ -62,7 +65,8 @@ void OnEnable()
6265
m_EnableAmbientProbeProp = serializedObject.FindProperty("m_EnableAmbientProbe");
6366
m_ShadowTypeProp = serializedObject.FindProperty("m_ShadowType");
6467
m_ShadowNearPlaneOffsetProp = serializedObject.FindProperty("m_ShadowNearPlaneOffset");
65-
m_ShadowBiasProperty = serializedObject.FindProperty("m_ShadowBias");
68+
m_ShadowMinNormalBiasProperty = serializedObject.FindProperty("m_MinShadowNormalBias");
69+
m_ShadowNormalBiasProperty = serializedObject.FindProperty("m_ShadowNormalBias");
6670
m_ShadowDistanceProp = serializedObject.FindProperty("m_ShadowDistance");
6771
m_ShadowAtlasResolutionProp = serializedObject.FindProperty("m_ShadowAtlasResolution");
6872
m_ShadowCascadesProp = serializedObject.FindProperty("m_ShadowCascades");
@@ -91,7 +95,8 @@ public override void OnInspectorGUI()
9195
EditorGUILayout.PropertyField(m_ShadowTypeProp, Styles.shadowType);
9296
EditorGUILayout.PropertyField(m_ShadowAtlasResolutionProp, Styles.shadowAtlasResolution);
9397
EditorGUILayout.PropertyField(m_ShadowNearPlaneOffsetProp, Styles.shadowNearPlaneOffset);
94-
EditorGUILayout.PropertyField(m_ShadowBiasProperty, Styles.shadowBias);
98+
EditorGUILayout.PropertyField(m_ShadowMinNormalBiasProperty, Styles.shadowMinBias);
99+
EditorGUILayout.PropertyField(m_ShadowNormalBiasProperty, Styles.shadowBias);
95100
EditorGUILayout.PropertyField(m_ShadowDistanceProp, Styles.shadowDistante);
96101
EditorGUILayout.PropertyField(m_ShadowCascadesProp, Styles.shadowCascades);
97102

Assets/LowEndMobilePipeline/LowEndMobilePipeline.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,9 @@ private void SetupLightShaderVariables(VisibleLight[] lights, int pixelLightCoun
247247
cmd.SetGlobalVectorArray("globalLightColor", m_LightColors);
248248
cmd.SetGlobalVectorArray("globalLightAtten", m_LightAttenuations);
249249
cmd.SetGlobalVectorArray("globalLightSpotDir", m_LightSpotDirections);
250-
cmd.SetGlobalVector("globalLightCount", new Vector4(pixelLightCount, totalLightCount, 0.0f, 0.0f));
250+
float shadowMinNormalBias = m_Asset.ShadowMinNormalBias;
251+
float shadowNormalBias = m_Asset.ShadowNormalBias;
252+
cmd.SetGlobalVector("globalLightData", new Vector4(pixelLightCount, totalLightCount, shadowMinNormalBias, shadowNormalBias));
251253
SetShaderKeywords(cmd, vertexLightCount > 0);
252254
context.ExecuteCommandBuffer(cmd);
253255
cmd.Dispose();
@@ -361,8 +363,6 @@ private void RenderShadowSlice(ref ScriptableRenderContext context, Vector3 ligh
361363
buffer.SetViewport(new Rect(m_ShadowSlices[cascadeIndex].atlasX, m_ShadowSlices[cascadeIndex].atlasY,
362364
m_ShadowSlices[cascadeIndex].shadowResolution, m_ShadowSlices[cascadeIndex].shadowResolution));
363365
buffer.SetViewProjectionMatrices(view, proj);
364-
buffer.SetGlobalVector("_WorldLightDirAndBias",
365-
new Vector4(-lightDir.x, -lightDir.y, -lightDir.z, m_Asset.ShadowBias));
366366
context.ExecuteCommandBuffer(buffer);
367367
buffer.Dispose();
368368

Assets/LowEndMobilePipeline/LowEndMobilePipelineAsset.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ protected override IRenderPipeline InternalCreatePipeline()
6161

6262
[SerializeField] private float m_ShadowDistance = 50.0f;
6363

64-
[SerializeField] private float m_ShadowBias = 0.0005f;
64+
[SerializeField] private float m_MinShadowNormalBias = 0.0005f;
65+
66+
[SerializeField] private float m_ShadowNormalBias = 0.05f;
6567

6668
[SerializeField] private ShadowCascades m_ShadowCascades = ShadowCascades.NO_CASCADES;
6769

@@ -117,10 +119,16 @@ public float ShadowDistance
117119
private set { m_ShadowDistance = value; }
118120
}
119121

120-
public float ShadowBias
122+
public float ShadowMinNormalBias
123+
{
124+
get { return m_MinShadowNormalBias; }
125+
private set { m_MinShadowNormalBias = value; }
126+
}
127+
128+
public float ShadowNormalBias
121129
{
122-
get { return m_ShadowBias; }
123-
private set { m_ShadowBias = value; }
130+
get { return m_ShadowNormalBias; }
131+
private set { m_ShadowNormalBias = value; }
124132
}
125133

126134
public int CascadeCount

Assets/LowEndMobilePipeline/Shaders/LowEndMobilePipeline.shader

Lines changed: 33 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,14 @@ Shader "ScriptableRenderPipeline/LowEndMobile/NonPBR"
114114

115115
#if defined(_VERTEX_LIGHTS)
116116
half4 diffuseAndSpecular = half4(1.0, 1.0, 1.0, 1.0);
117-
for (int lightIndex = globalLightCount.x; lightIndex < globalLightCount.y; ++lightIndex)
117+
for (int lightIndex = globalLightData.x; lightIndex < globalLightData.y; ++lightIndex)
118118
{
119119
LightInput lightInput;
120120
INITIALIZE_LIGHT(lightInput, lightIndex);
121121
o.fogCoord.yzw += EvaluateOneLight(lightInput, diffuseAndSpecular.rgb, diffuseAndSpecular, normal, o.posWS, o.viewDir.xyz);
122122
}
123123
#endif
124124

125-
#ifndef _SHADOW_CASCADES
126-
o.shadowCoord = mul(_WorldToShadow[0], float4(o.posWS, 1.0));
127-
#endif
128-
129125
#ifdef _LIGHT_PROBES_ON
130126
o.fogCoord.yzw += max(half3(0, 0, 0), ShadeSH9(half4(normal, 1)));
131127
#endif
@@ -152,23 +148,24 @@ Shader "ScriptableRenderPipeline/LowEndMobile/NonPBR"
152148
half4 specularGloss;
153149
SpecularGloss(i.uv01.xy, diffuse, alpha, specularGloss);
154150

155-
#ifdef _SHADOWS
156-
half shadowAttenuation = ComputeShadowAttenuation(i);
157-
#else
158-
half shadowAttenuation = 1.0f;
159-
#endif
160151
half3 viewDir = i.viewDir.xyz;
161152

162153
// TODO: Restrict pixel lights by 4. This way we can keep moderate constrain for most LD project
163154
// and can benefit from better data layout/avoid branching by doing vec math.
164155
half3 color = half3(0, 0, 0);
165-
for (int lightIndex = 0; lightIndex < globalLightCount.x; ++lightIndex)
156+
for (int lightIndex = 0; lightIndex < globalLightData.x; ++lightIndex)
166157
{
167-
LightInput additionalLight;
168-
INITIALIZE_LIGHT(additionalLight, lightIndex);
169-
color += EvaluateOneLight(additionalLight, diffuse, specularGloss, normal, i.posWS, viewDir);
158+
LightInput lightData;
159+
half NdotL;
160+
INITIALIZE_LIGHT(lightData, lightIndex);
161+
color += EvaluateOneLight(lightData, diffuse, specularGloss, normal, i.posWS, viewDir, NdotL);
162+
#ifdef _SHADOWS
170163
if (lightIndex == 0)
171-
color *= shadowAttenuation;
164+
{
165+
float bias = max(globalLightData.z, (1.0 - NdotL) * globalLightData.w);
166+
color *= ComputeShadowAttenuation(i, i.normal * bias);
167+
}
168+
#endif
172169
}
173170

174171
half3 emissionColor;
@@ -210,39 +207,11 @@ Shader "ScriptableRenderPipeline/LowEndMobile/NonPBR"
210207
#pragma vertex vert
211208
#pragma fragment frag
212209

213-
float4 _WorldLightDirAndBias;
214-
215210
#include "UnityCG.cginc"
216211

217-
struct VertexInput
218-
{
219-
float4 pos : POSITION;
220-
float3 normal : NORMAL;
221-
};
222-
223-
// Similar to UnityClipSpaceShadowCasterPos but using LDPipeline lightdir and bias and applying near plane clamp
224-
float4 ClipSpaceShadowCasterPos(float4 vertex, float3 normal)
212+
float4 vert(float4 pos : POSITION) : SV_POSITION
225213
{
226-
float4 wPos = mul(unity_ObjectToWorld, vertex);
227-
228-
if (_WorldLightDirAndBias.w > 0.0)
229-
{
230-
float3 wNormal = UnityObjectToWorldNormal(normal);
231-
232-
// apply normal offset bias (inset position along the normal)
233-
// bias needs to be scaled by sine between normal and light direction
234-
// (http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/)
235-
//
236-
// _WorldLightDirAndBias.w shadow bias defined in LRRenderPipeline asset
237-
238-
float shadowCos = dot(wNormal, _WorldLightDirAndBias.xyz);
239-
float shadowSine = sqrt(1 - shadowCos*shadowCos);
240-
float normalBias = _WorldLightDirAndBias.w * shadowSine;
241-
242-
wPos.xyz -= wNormal * normalBias;
243-
}
244-
245-
float4 clipPos = mul(UNITY_MATRIX_VP, wPos);
214+
float4 clipPos = UnityObjectToClipPos(pos);
246215
#if defined(UNITY_REVERSED_Z)
247216
clipPos.z = min(clipPos.z, UNITY_NEAR_CLIP_VALUE);
248217
#else
@@ -251,40 +220,35 @@ Shader "ScriptableRenderPipeline/LowEndMobile/NonPBR"
251220
return clipPos;
252221
}
253222

254-
float4 vert(VertexInput i) : SV_POSITION
255-
{
256-
return ClipSpaceShadowCasterPos(i.pos, i.normal);
257-
}
258-
259223
half4 frag() : SV_TARGET
260224
{
261225
return 0;
262226
}
263227
ENDCG
264228
}
265229

266-
// This pass it not used during regular rendering, only for lightmap baking.
267-
Pass
268-
{
269-
Name "LD_META"
270-
Tags{ "LightMode" = "Meta" }
230+
// This pass it not used during regular rendering, only for lightmap baking.
231+
Pass
232+
{
233+
Name "LD_META"
234+
Tags{ "LightMode" = "Meta" }
271235

272-
Cull Off
236+
Cull Off
273237

274-
CGPROGRAM
275-
#pragma vertex vert_meta
276-
#pragma fragment frag_meta
238+
CGPROGRAM
239+
#pragma vertex vert_meta
240+
#pragma fragment frag_meta
277241

278-
#pragma shader_feature _EMISSION
279-
#pragma shader_feature _METALLICGLOSSMAP
280-
#pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
281-
#pragma shader_feature ___ _DETAIL_MULX2
282-
#pragma shader_feature EDITOR_VISUALIZATION
242+
#pragma shader_feature _EMISSION
243+
#pragma shader_feature _METALLICGLOSSMAP
244+
#pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
245+
#pragma shader_feature ___ _DETAIL_MULX2
246+
#pragma shader_feature EDITOR_VISUALIZATION
283247

284-
#include "UnityStandardMeta.cginc"
285-
ENDCG
286-
}
248+
#include "UnityStandardMeta.cginc"
249+
ENDCG
287250
}
288-
Fallback "Standard (Specular setup)"
289-
CustomEditor "LowendMobilePipelineMaterialEditor"
251+
}
252+
Fallback "Standard (Specular setup)"
253+
CustomEditor "LowendMobilePipelineMaterialEditor"
290254
}

Assets/LowEndMobilePipeline/Shaders/LowEndMobilePipelineCore.cginc

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,6 @@ struct v2f
4545
#endif
4646
half4 viewDir : TEXCOORD5; // xyz: viewDir
4747
UNITY_FOG_COORDS_PACKED(6, half4) // x: fogCoord, yzw: vertexColor
48-
#ifndef _SHADOW_CASCADES
49-
float4 shadowCoord : TEXCOORD7;
50-
#endif
5148
float4 hpos : SV_POSITION;
5249
};
5350

@@ -58,12 +55,12 @@ half4 globalLightColor[MAX_LIGHTS];
5855
float4 globalLightPos[MAX_LIGHTS];
5956
half4 globalLightSpotDir[MAX_LIGHTS];
6057
half4 globalLightAtten[MAX_LIGHTS];
61-
int4 globalLightCount; // x: pixelLightCount, y = totalLightCount (pixel + vert)
58+
float4 globalLightData; // x: pixelLightCount, y = totalLightCount (pixel + vert), z = minShadowNormalBiasOffset, w = shadowNormalBiasOffset
6259

6360
sampler2D_float _ShadowMap;
6461
float _PCFKernel[8];
6562

66-
half4x4 _WorldToShadow[MAX_SHADOW_CASCADES];
63+
float4x4 _WorldToShadow[MAX_SHADOW_CASCADES];
6764
float4 _DirShadowSplitSpheres[MAX_SHADOW_CASCADES];
6865
half _Shininess;
6966
samplerCUBE _Cube;
@@ -131,12 +128,12 @@ inline half ComputeCascadeIndex(float3 wpos)
131128
return 4 - dot(weights, fixed4(4, 3, 2, 1));
132129
}
133130

134-
inline half ShadowAttenuation(half3 shadowCoord)
131+
inline half ShadowAttenuation(float3 shadowCoord)
135132
{
136133
if (shadowCoord.x <= 0 || shadowCoord.x >= 1 || shadowCoord.y <= 0 || shadowCoord.y >= 1)
137134
return 1;
138135

139-
half depth = tex2D(_ShadowMap, shadowCoord).r;
136+
float depth = tex2D(_ShadowMap, shadowCoord).r;
140137
#if defined(UNITY_REVERSED_Z)
141138
return step(depth, shadowCoord.z);
142139
#else
@@ -155,22 +152,22 @@ inline half ShadowPCF(half3 shadowCoord)
155152
return attenuation * 0.25;
156153
}
157154

158-
inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half4 specularGloss, half3 normal, float3 posWorld, half3 viewDir)
155+
inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half4 specularGloss, half3 normal, float3 posWorld, half3 viewDir, out half NdotL)
159156
{
160157
float3 posToLight = lightInput.pos.xyz;
161158
posToLight -= posWorld * lightInput.pos.w;
162159

163160
float distanceSqr = max(dot(posToLight, posToLight), 0.001);
164161
float lightAtten = 1.0 / (1.0 + distanceSqr * lightInput.atten.z);
165162

166-
half3 lightDir = posToLight * rsqrt(distanceSqr);
163+
float3 lightDir = posToLight * rsqrt(distanceSqr);
167164
half SdotL = saturate(dot(lightInput.spotDir.xyz, lightDir));
168165
lightAtten *= saturate((SdotL - lightInput.atten.x) / lightInput.atten.y);
169166

170167
half cutoff = step(distanceSqr, lightInput.atten.w);
171168
lightAtten *= cutoff;
172169

173-
half NdotL = saturate(dot(normal, lightDir));
170+
NdotL = saturate(dot(normal, lightDir));
174171

175172
half3 halfVec = normalize(lightDir + viewDir);
176173
half NdotH = saturate(dot(normal, halfVec));
@@ -186,20 +183,17 @@ inline half3 EvaluateOneLight(LightInput lightInput, half3 diffuseColor, half4 s
186183
#endif
187184
}
188185

189-
inline half ComputeShadowAttenuation(v2f i)
186+
inline half ComputeShadowAttenuation(v2f i, float3 offset)
190187
{
191-
#ifndef _SHADOW_CASCADES
192-
half4 shadowCoord;
193-
shadowCoord = i.shadowCoord;
194-
#else
195-
half4 shadowCoord;
196-
int cascadeIndex = ComputeCascadeIndex(i.posWS);
188+
float3 posWorldOffsetNormal = i.posWS + offset;
189+
int cascadeIndex = 0;
190+
191+
#ifdef _SHADOW_CASCADES
192+
cascadeIndex = ComputeCascadeIndex(i.posWS);
197193
if (cascadeIndex < 4)
198-
shadowCoord = mul(_WorldToShadow[cascadeIndex], half4(i.posWS, 1.0));
199-
else
200194
return 1.0;
201195
#endif
202-
196+
float4 shadowCoord = mul(_WorldToShadow[cascadeIndex], float4(posWorldOffsetNormal, 1.0));
203197
shadowCoord.xyz /= shadowCoord.w;
204198
shadowCoord.z = saturate(shadowCoord.z);
205199

0 commit comments

Comments
 (0)