Skip to content

Commit 91f7f80

Browse files
committed
Fixed a number of issues with shadow rendering
1 parent 36459ce commit 91f7f80

File tree

4 files changed

+113
-189
lines changed

4 files changed

+113
-189
lines changed

com.unity.render-pipelines.universal/Runtime/2D/Shadows/ShadowCaster2D.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ public class ShadowCaster2D : ShadowCasterGroup2D
2626
internal ShadowCasterGroup2D m_ShadowCasterGroup = null;
2727
internal ShadowCasterGroup2D m_PreviousShadowCasterGroup = null;
2828

29-
internal Mesh mesh => m_Mesh;
30-
internal Vector3[] shapePath => m_ShapePath;
29+
internal BoundingSphere m_ProjectedBoundingSphere;
30+
31+
public Mesh mesh => m_Mesh;
32+
public Vector3[] shapePath => m_ShapePath;
3133
internal int shapePathHash { get { return m_ShapePathHash; } set { m_ShapePathHash = value; } }
3234

3335
int m_PreviousShadowGroup = 0;
3436
bool m_PreviousCastsShadows = true;
3537
int m_PreviousPathHash = 0;
3638

37-
3839
/// <summary>
3940
/// If selfShadows is true, useRendererSilhoutte specifies that the renderer's sihouette should be considered part of the shadow. If selfShadows is false, useRendererSilhoutte specifies that the renderer's sihouette should be excluded from the shadow
4041
/// </summary>
@@ -75,6 +76,14 @@ static int[] SetDefaultSortingLayers()
7576
return allLayers;
7677
}
7778

79+
internal bool IsLit(Light2D light)
80+
{
81+
Vector3 deltaPos = light.transform.position - m_ProjectedBoundingSphere.position;
82+
float distanceSq = deltaPos.x * deltaPos.x + deltaPos.y * deltaPos.y;
83+
84+
return distanceSq <= (light.boundingSphere.radius + m_ProjectedBoundingSphere.radius);
85+
}
86+
7887
internal bool IsShadowedLayer(int layer)
7988
{
8089
return m_ApplyToSortingLayers != null ? Array.IndexOf(m_ApplyToSortingLayers, layer) >= 0 : false;
@@ -126,7 +135,7 @@ protected void OnEnable()
126135
if (m_Mesh == null || m_InstanceId != GetInstanceID())
127136
{
128137
m_Mesh = new Mesh();
129-
ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
138+
m_ProjectedBoundingSphere = ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
130139
m_InstanceId = GetInstanceID();
131140
}
132141

@@ -145,7 +154,9 @@ public void Update()
145154

146155
bool rebuildMesh = LightUtility.CheckForChange(m_ShapePathHash, ref m_PreviousPathHash);
147156
if (rebuildMesh)
148-
ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
157+
{
158+
m_ProjectedBoundingSphere = ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
159+
}
149160

150161
m_PreviousShadowCasterGroup = m_ShadowCasterGroup;
151162
bool addedToNewGroup = ShadowCasterGroup2DManager.AddToShadowCasterGroup(this, ref m_ShadowCasterGroup);

com.unity.render-pipelines.universal/Runtime/2D/Shadows/ShadowRendering.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,13 @@ public static void RenderShadows(IRenderPass2D pass, RenderingData renderingData
174174
{
175175
var shadowCaster = shadowCasters[i];
176176

177-
if (shadowCaster != null && projectedShadowsMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
177+
if (shadowCaster.IsLit(light))
178178
{
179-
if (shadowCaster.castsShadows)
180-
cmdBuffer.DrawMesh(shadowCaster.mesh, shadowCaster.transform.localToWorldMatrix, projectedShadowsMaterial, 0, 0);
179+
if (shadowCaster != null && projectedShadowsMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
180+
{
181+
if (shadowCaster.castsShadows)
182+
cmdBuffer.DrawMesh(shadowCaster.mesh, shadowCaster.transform.localToWorldMatrix, projectedShadowsMaterial, 0, 0);
183+
}
181184
}
182185
}
183186

@@ -218,10 +221,14 @@ public static void RenderShadows(IRenderPass2D pass, RenderingData renderingData
218221
{
219222
var shadowCaster = shadowCasters[i];
220223

221-
if (shadowCaster != null && projectedShadowsMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
224+
if (shadowCaster.IsLit(light))
222225
{
223-
if (shadowCaster.castsShadows)
224-
cmdBuffer.DrawMesh(shadowCaster.mesh, shadowCaster.transform.localToWorldMatrix, projectedShadowsMaterial, 0, 1);
226+
227+
if (shadowCaster != null && projectedShadowsMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
228+
{
229+
if (shadowCaster.castsShadows)
230+
cmdBuffer.DrawMesh(shadowCaster.mesh, shadowCaster.transform.localToWorldMatrix, projectedShadowsMaterial, 0, 1);
231+
}
225232
}
226233
}
227234
}

com.unity.render-pipelines.universal/Runtime/2D/Shadows/ShadowUtility.cs

Lines changed: 54 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -11,164 +11,73 @@ namespace UnityEngine.Experimental.Rendering.Universal
1111
{
1212
internal class ShadowUtility
1313
{
14-
internal struct Edge : IComparable<Edge>
14+
// I need another function to generate the mesh from the outline.
15+
public static BoundingSphere GenerateShadowMesh(Mesh mesh, Vector3[] shapePath)
1516
{
16-
public int vertexIndex0;
17-
public int vertexIndex1;
18-
public Vector4 tangent;
19-
private bool compareReversed; // This is done so that edge AB can equal edge BA
17+
Debug.AssertFormat(shapePath.Length > 3, "Shadow shape path must have 3 or more vertices");
2018

21-
public void AssignVertexIndices(int vi0, int vi1)
22-
{
23-
vertexIndex0 = vi0;
24-
vertexIndex1 = vi1;
25-
compareReversed = vi0 > vi1;
26-
}
27-
28-
public int Compare(Edge a, Edge b)
29-
{
30-
int adjustedVertexIndex0A = a.compareReversed ? a.vertexIndex1 : a.vertexIndex0;
31-
int adjustedVertexIndex1A = a.compareReversed ? a.vertexIndex0 : a.vertexIndex1;
32-
int adjustedVertexIndex0B = b.compareReversed ? b.vertexIndex1 : b.vertexIndex0;
33-
int adjustedVertexIndex1B = b.compareReversed ? b.vertexIndex0 : b.vertexIndex1;
34-
35-
// Sort first by VI0 then by VI1
36-
int deltaVI0 = adjustedVertexIndex0A - adjustedVertexIndex0B;
37-
int deltaVI1 = adjustedVertexIndex1A - adjustedVertexIndex1B;
38-
39-
if (deltaVI0 == 0)
40-
return deltaVI1;
41-
else
42-
return deltaVI0;
43-
}
44-
45-
public int CompareTo(Edge edgeToCompare)
46-
{
47-
return Compare(this, edgeToCompare);
48-
}
49-
}
50-
51-
static Edge CreateEdge(int triangleIndexA, int triangleIndexB, List<Vector3> vertices, List<int> triangles)
52-
{
53-
Edge retEdge = new Edge();
54-
55-
retEdge.AssignVertexIndices(triangles[triangleIndexA], triangles[triangleIndexB]);
56-
57-
Vector3 vertex0 = vertices[retEdge.vertexIndex0];
58-
vertex0.z = 0;
59-
Vector3 vertex1 = vertices[retEdge.vertexIndex1];
60-
vertex1.z = 0;
61-
62-
Vector3 edgeDir = Vector3.Normalize(vertex1 - vertex0);
63-
retEdge.tangent = Vector3.Cross(-Vector3.forward, edgeDir);
64-
65-
return retEdge;
66-
}
67-
68-
static void PopulateEdgeArray(List<Vector3> vertices, List<int> triangles, List<Edge> edges)
69-
{
70-
for (int triangleIndex = 0; triangleIndex < triangles.Count; triangleIndex += 3)
71-
{
72-
edges.Add(CreateEdge(triangleIndex, triangleIndex + 1, vertices, triangles));
73-
edges.Add(CreateEdge(triangleIndex + 1, triangleIndex + 2, vertices, triangles));
74-
edges.Add(CreateEdge(triangleIndex + 2, triangleIndex, vertices, triangles));
75-
}
76-
}
77-
78-
static bool IsOutsideEdge(int edgeIndex, List<Edge> edgesToProcess)
79-
{
80-
int previousIndex = edgeIndex - 1;
81-
int nextIndex = edgeIndex + 1;
82-
int numberOfEdges = edgesToProcess.Count;
83-
Edge currentEdge = edgesToProcess[edgeIndex];
84-
85-
return (previousIndex < 0 || (currentEdge.CompareTo(edgesToProcess[edgeIndex - 1]) != 0)) && (nextIndex >= numberOfEdges || (currentEdge.CompareTo(edgesToProcess[edgeIndex + 1]) != 0));
86-
}
19+
List<Vector3> vertices = new List<Vector3>();
20+
List<int> triangles = new List<int>();
21+
List<Vector4> normals = new List<Vector4>();
8722

88-
static void SortEdges(List<Edge> edgesToProcess)
89-
{
90-
edgesToProcess.Sort();
91-
}
23+
float minX = float.MaxValue;
24+
float maxX = float.MinValue;
25+
float minY = float.MaxValue;
26+
float maxY = float.MinValue;
9227

93-
static void CreateShadowTriangles(List<Vector3> vertices, List<Color> colors, List<int> triangles, List<Vector4> tangents, List<Edge> edges)
94-
{
95-
for (int edgeIndex = 0; edgeIndex < edges.Count; edgeIndex++)
28+
// Add outline vertices
29+
int pathLength = shapePath.Length;
30+
for (int i = 0; i < pathLength; i++)
9631
{
97-
if (IsOutsideEdge(edgeIndex, edges))
98-
{
99-
Edge edge = edges[edgeIndex];
100-
tangents[edge.vertexIndex1] = -edge.tangent;
101-
102-
int newVertexIndex = vertices.Count;
103-
vertices.Add(vertices[edge.vertexIndex0]);
104-
colors.Add(colors[edge.vertexIndex0]);
105-
106-
tangents.Add(-edge.tangent);
107-
108-
triangles.Add(edge.vertexIndex0);
109-
triangles.Add(newVertexIndex);
110-
triangles.Add(edge.vertexIndex1);
111-
}
32+
vertices.Add(shapePath[i]);
33+
normals.Add(Vector3.zero);
11234
}
113-
}
114-
115-
static object InterpCustomVertexData(Vec3 position, object[] data, float[] weights)
116-
{
117-
return data[0];
118-
}
11935

120-
static void InitializeTangents(int tangentsToAdd, List<Vector4> tangents)
121-
{
122-
for (int i = 0; i < tangentsToAdd; i++)
123-
tangents.Add(Vector4.zero);
124-
}
125-
126-
public static void GenerateShadowMesh(Mesh mesh, Vector3[] shapePath)
127-
{
128-
List<Vector3> vertices = new List<Vector3>();
129-
List<int> triangles = new List<int>();
130-
List<Vector4> tangents = new List<Vector4>();
131-
List<Color> extrusion = new List<Color>();
132-
133-
// Create interior geometry
134-
int pointCount = shapePath.Length;
135-
var inputs = new ContourVertex[pointCount];
136-
for (int i = 0; i < pointCount; i++)
36+
// Add extrusion vertices, normals, and triangles
37+
int vertexCount = pathLength;
38+
for (int i=0;i<pathLength;i++)
13739
{
138-
Color extrusionData = new Color(shapePath[i].x, shapePath[i].y, shapePath[i].x, shapePath[i].y);
139-
int nextPoint = (i + 1) % pointCount;
140-
inputs[i] = new ContourVertex() { Position = new Vec3() { X = shapePath[i].x, Y = shapePath[i].y, Z = 0 }, Data = extrusionData };
40+
int startIndex = i;
41+
int endIndex = (i + 1) % pathLength;
42+
43+
Vector3 start = shapePath[startIndex];
44+
Vector3 end = shapePath[endIndex];
45+
46+
Vector4 normal = Vector3.Cross(Vector3.Normalize(end - start), -Vector3.forward);
47+
normal = new Vector4(normal.x, normal.y, end.x, end.y);
48+
normals.Add(normal);
49+
normal = new Vector4(normal.x, normal.y, start.x, start.y);
50+
normals.Add(normal);
51+
52+
// Triangle 1
53+
triangles.Add(startIndex);
54+
triangles.Add(vertexCount);
55+
triangles.Add(vertexCount+1);
56+
// Triangle 2
57+
triangles.Add(vertexCount+1);
58+
triangles.Add(endIndex);
59+
triangles.Add(startIndex);
60+
61+
vertices.Add(start);
62+
vertices.Add(end);
63+
64+
vertexCount += 2;
14165
}
14266

143-
Tess tessI = new Tess();
144-
tessI.AddContour(inputs, ContourOrientation.Original);
145-
tessI.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3, InterpCustomVertexData);
146-
147-
var indicesI = tessI.Elements.Select(i => i).ToArray();
148-
var verticesI = tessI.Vertices.Select(v => new Vector3(v.Position.X, v.Position.Y, 0)).ToArray();
149-
var extrusionI = tessI.Vertices.Select(v => new Color(((Color)v.Data).r, ((Color)v.Data).g, ((Color)v.Data).b, ((Color)v.Data).a)).ToArray();
150-
151-
vertices.AddRange(verticesI);
152-
triangles.AddRange(indicesI);
153-
extrusion.AddRange(extrusionI);
154-
155-
InitializeTangents(vertices.Count, tangents);
67+
mesh.Clear();
68+
mesh.vertices = vertices.ToArray();
69+
mesh.triangles = triangles.ToArray();
70+
mesh.tangents = normals.ToArray();
15671

157-
List<Edge> edges = new List<Edge>();
158-
PopulateEdgeArray(vertices, triangles, edges);
159-
SortEdges(edges);
160-
CreateShadowTriangles(vertices, extrusion, triangles, tangents, edges);
72+
// Calculate bounding sphere (circle)
73+
Vector3 origin = new Vector2(0.5f * (minX + maxX), 0.5f * (minY + maxY));
74+
float deltaX = maxX - minX;
75+
float deltaY = maxY - minY;
76+
float radius = 0.5f * Mathf.Sqrt(deltaX * deltaX + deltaY * deltaY);
16177

162-
Color[] finalExtrusion = extrusion.ToArray();
163-
Vector3[] finalVertices = vertices.ToArray();
164-
int[] finalTriangles = triangles.ToArray();
165-
Vector4[] finalTangents = tangents.ToArray();
78+
BoundingSphere retSphere = new BoundingSphere(origin, radius);
16679

167-
mesh.Clear();
168-
mesh.vertices = finalVertices;
169-
mesh.triangles = finalTriangles;
170-
mesh.tangents = finalTangents;
171-
mesh.colors = finalExtrusion;
80+
return retSphere;
17281
}
17382
}
17483
}

com.unity.render-pipelines.universal/Shaders/2D/Include/ShadowProjectVertex.hlsl

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,50 @@
11
#if !defined(SHADOW_PROJECT_VERTEX)
22
#define SHADOW_PROJECT_VERTEX
33

4-
struct Attributes\
5-
{\
6-
float3 vertex : POSITION;\
7-
float4 tangent: TANGENT;\
8-
float4 extrusion : COLOR;\
9-
};\
10-
11-
struct Varyings\
12-
{\
13-
float4 vertex : SV_POSITION;\
14-
};\
15-
16-
uniform float3 _LightPos;\
4+
#define SQUARE_ROOT_2 1.4142136f
5+
6+
struct Attributes
7+
{
8+
float3 vertex : POSITION;
9+
float4 tangent: TANGENT;
10+
};
11+
12+
struct Varyings
13+
{
14+
float4 vertex : SV_POSITION;
15+
};
16+
17+
uniform float3 _LightPos;
1718
uniform float _ShadowRadius;
1819

1920

2021
Varyings ProjectShadow(Attributes v)
2122
{
2223
Varyings o;
23-
float3 vertexWS = TransformObjectToWorld(v.vertex); // This should be in world space
24-
float3 unnormalizedLightDir = _LightPos - vertexWS;
25-
unnormalizedLightDir.z = 0;
24+
float3 vertexWS0 = TransformObjectToWorld(float3(v.vertex.xy,0)); // This should be in world space
25+
float3 vertexWS1 = TransformObjectToWorld(float3(v.tangent.zw,0)); // the tangent has
26+
float3 unnormalizedLightDir0 = _LightPos - vertexWS0;
27+
float3 unnormalizedLightDir1 = _LightPos - vertexWS1;
28+
29+
float3 lightDir0 = normalize(unnormalizedLightDir0);
30+
float3 lightDir1 = normalize(unnormalizedLightDir1);
31+
float3 avgLightDir = normalize(lightDir0 + lightDir1);
2632

2733
// Start of code to see if this point should be extruded
28-
float3 lightDir = normalize(unnormalizedLightDir);
29-
float3 shadowDir = -lightDir;
30-
float3 worldTangent = TransformObjectToWorldDir(v.tangent.xyz);
3134

32-
// We need to solve to make sure our length will be long enough to be in our circle. Use similar triangles. h0/d0 = h1/d1 => h1 = d1 * h0 / d0 => h1 = radius * h0 / d0
33-
// d0 is distance to the side (from the light)
34-
// h0 is distance to the vertex (from the light).
35-
// d1 is the light radius
36-
// h1 is the length of the projection (from the light)
37-
// shadow length = h1 - h0
3835

39-
float h0 = length(float2(unnormalizedLightDir.x, unnormalizedLightDir.y));
40-
float d0 = dot(unnormalizedLightDir, worldTangent);
41-
float shadowLength = max((_ShadowRadius * h0 / d0) - d0, 0);
36+
float shadowLength = _ShadowRadius / dot(-lightDir0, -avgLightDir);
37+
38+
float3 normalWS = TransformObjectToWorldDir(float3(v.tangent.xy, 0));
39+
4240

4341
// Tests to make sure the light is between 0-90 degrees to the normal. Will be one if it is, zero if not.
44-
float sharedShadowTest = saturate(ceil(dot(lightDir, worldTangent)));
45-
46-
//float3 sharedShadowOffset = sharedShadowTest * shadowLength * shadowDir;
47-
float3 sharedShadowOffset = sharedShadowTest * shadowLength * shadowDir;
42+
float3 shadowDir = -lightDir0;
43+
float shadowTest = dot(lightDir0, normalWS) < 0;
44+
float3 shadowOffset = shadowTest * shadowLength * shadowDir;
4845

4946
float3 position;
50-
position = vertexWS + sharedShadowOffset;
47+
position = shadowTest * (_LightPos + shadowOffset) + (1-shadowTest) * vertexWS0;
5148

5249
o.vertex = TransformWorldToHClip(position);
5350

0 commit comments

Comments
 (0)