testss
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
/// <summary>
|
||||
/// (Deprecated) An add-on module for Cinemachine Virtual Camera that tweaks the orthographic size
|
||||
/// of the virtual camera. It detects the presence of the Pixel Perfect Camera component and use the
|
||||
/// settings from that Pixel Perfect Camera to correct the orthographic size so that pixel art
|
||||
/// sprites would appear pixel perfect when the virtual camera becomes live.
|
||||
/// </summary>
|
||||
[AddComponentMenu("")] // Hide in menu
|
||||
public class CinemachineUniversalPixelPerfect : MonoBehaviour
|
||||
{
|
||||
void OnEnable()
|
||||
{
|
||||
Debug.LogError("CinemachineUniversalPixelPerfect is now deprecated and doesn't function properly. Instead, use the one from Cinemachine v2.4.0 or newer.");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,351 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
/// <summary>
|
||||
/// Class <c>Light2D</c> is a 2D light which can be used with the 2D Renderer.
|
||||
/// </summary>
|
||||
///
|
||||
[ExecuteAlways, DisallowMultipleComponent]
|
||||
[AddComponentMenu("Rendering/2D/Light 2D")]
|
||||
[HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2DLightProperties.html")]
|
||||
public sealed partial class Light2D : MonoBehaviour, ISerializationCallbackReceiver
|
||||
{
|
||||
public enum DeprecatedLightType
|
||||
{
|
||||
Parametric = 0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// an enumeration of the types of light
|
||||
/// </summary>
|
||||
public enum LightType
|
||||
{
|
||||
Parametric = 0,
|
||||
Freeform = 1,
|
||||
Sprite = 2,
|
||||
Point = 3,
|
||||
Global = 4
|
||||
}
|
||||
|
||||
public enum NormalMapQuality
|
||||
{
|
||||
Disabled = 2,
|
||||
Fast = 0,
|
||||
Accurate = 1
|
||||
}
|
||||
|
||||
public enum OverlapOperation
|
||||
{
|
||||
Additive,
|
||||
AlphaBlend
|
||||
}
|
||||
|
||||
|
||||
public enum ComponentVersions
|
||||
{
|
||||
Version_Unserialized = 0,
|
||||
Version_1 = 1
|
||||
}
|
||||
|
||||
const ComponentVersions k_CurrentComponentVersion = ComponentVersions.Version_1;
|
||||
[SerializeField] ComponentVersions m_ComponentVersion = ComponentVersions.Version_Unserialized;
|
||||
|
||||
|
||||
#if USING_ANIMATION_MODULE
|
||||
[UnityEngine.Animations.NotKeyable]
|
||||
#endif
|
||||
[SerializeField] LightType m_LightType = LightType.Point;
|
||||
[SerializeField, FormerlySerializedAs("m_LightOperationIndex")]
|
||||
int m_BlendStyleIndex = 0;
|
||||
|
||||
[SerializeField] float m_FalloffIntensity = 0.5f;
|
||||
|
||||
[ColorUsage(true)]
|
||||
[SerializeField] Color m_Color = Color.white;
|
||||
[SerializeField] float m_Intensity = 1;
|
||||
|
||||
[FormerlySerializedAs("m_LightVolumeOpacity")]
|
||||
[SerializeField] float m_LightVolumeIntensity = 1.0f;
|
||||
[SerializeField] bool m_LightVolumeIntensityEnabled = false;
|
||||
[SerializeField] int[] m_ApplyToSortingLayers = new int[1]; // These are sorting layer IDs. If we need to update this at runtime make sure we add code to update global lights
|
||||
|
||||
[Reload("Textures/2D/Sparkle.png")]
|
||||
[SerializeField] Sprite m_LightCookieSprite;
|
||||
|
||||
[FormerlySerializedAs("m_LightCookieSprite")]
|
||||
[SerializeField] Sprite m_DeprecatedPointLightCookieSprite;
|
||||
|
||||
[SerializeField] int m_LightOrder = 0;
|
||||
|
||||
[SerializeField] OverlapOperation m_OverlapOperation = OverlapOperation.Additive;
|
||||
|
||||
[FormerlySerializedAs("m_PointLightDistance")]
|
||||
[SerializeField] float m_NormalMapDistance = 3.0f;
|
||||
|
||||
#if USING_ANIMATION_MODULE
|
||||
[UnityEngine.Animations.NotKeyable]
|
||||
#endif
|
||||
[FormerlySerializedAs("m_PointLightQuality")]
|
||||
[SerializeField] NormalMapQuality m_NormalMapQuality = NormalMapQuality.Disabled;
|
||||
|
||||
[SerializeField] bool m_UseNormalMap = false; // This is now deprecated. Keep it here for backwards compatibility.
|
||||
|
||||
[SerializeField] bool m_ShadowIntensityEnabled = false;
|
||||
[Range(0, 1)]
|
||||
[SerializeField] float m_ShadowIntensity = 0.75f;
|
||||
|
||||
[SerializeField] bool m_ShadowVolumeIntensityEnabled = false;
|
||||
[Range(0, 1)]
|
||||
[SerializeField] float m_ShadowVolumeIntensity = 0.75f;
|
||||
|
||||
Mesh m_Mesh;
|
||||
|
||||
[SerializeField]
|
||||
private LightUtility.LightMeshVertex[] m_Vertices = new LightUtility.LightMeshVertex[1];
|
||||
|
||||
[SerializeField]
|
||||
private ushort[] m_Triangles = new ushort[1];
|
||||
|
||||
internal LightUtility.LightMeshVertex[] vertices { get { return m_Vertices; } set { m_Vertices = value; } }
|
||||
|
||||
internal ushort[] indices { get { return m_Triangles; } set { m_Triangles = value; } }
|
||||
|
||||
// Transients
|
||||
int m_PreviousLightCookieSprite;
|
||||
internal int[] affectedSortingLayers => m_ApplyToSortingLayers;
|
||||
|
||||
private int lightCookieSpriteInstanceID => m_LightCookieSprite?.GetInstanceID() ?? 0;
|
||||
|
||||
[SerializeField]
|
||||
Bounds m_LocalBounds;
|
||||
internal BoundingSphere boundingSphere { get; private set; }
|
||||
|
||||
internal Mesh lightMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null == m_Mesh)
|
||||
m_Mesh = new Mesh();
|
||||
return m_Mesh;
|
||||
}
|
||||
}
|
||||
|
||||
internal bool hasCachedMesh => (vertices.Length > 1 && indices.Length > 1);
|
||||
|
||||
/// <summary>
|
||||
/// The lights current type
|
||||
/// </summary>
|
||||
public LightType lightType
|
||||
{
|
||||
get => m_LightType;
|
||||
set
|
||||
{
|
||||
if (m_LightType != value)
|
||||
UpdateMesh(true);
|
||||
|
||||
m_LightType = value;
|
||||
Light2DManager.ErrorIfDuplicateGlobalLight(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The lights current operation index
|
||||
/// </summary>
|
||||
public int blendStyleIndex { get => m_BlendStyleIndex; set => m_BlendStyleIndex = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the darkness of the shadow
|
||||
/// </summary>
|
||||
public float shadowIntensity { get => m_ShadowIntensity; set => m_ShadowIntensity = Mathf.Clamp01(value); }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that the shadows are enabled
|
||||
/// </summary>
|
||||
public bool shadowsEnabled { get => m_ShadowIntensityEnabled; set => m_ShadowIntensityEnabled = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the darkness of the shadow
|
||||
/// </summary>
|
||||
public float shadowVolumeIntensity { get => m_ShadowVolumeIntensity; set => m_ShadowVolumeIntensity = Mathf.Clamp01(value); }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies that the volumetric shadows are enabled
|
||||
/// </summary>
|
||||
public bool volumetricShadowsEnabled { get => m_ShadowVolumeIntensityEnabled; set => m_ShadowVolumeIntensityEnabled = value; }
|
||||
|
||||
/// <summary>
|
||||
/// The lights current color
|
||||
/// </summary>
|
||||
public Color color { get => m_Color; set => m_Color = value; }
|
||||
|
||||
/// <summary>
|
||||
/// The lights current intensity
|
||||
/// </summary>
|
||||
public float intensity { get => m_Intensity; set => m_Intensity = value; }
|
||||
|
||||
/// <summary>
|
||||
/// The lights current intensity
|
||||
/// </summary>
|
||||
///
|
||||
[Obsolete]
|
||||
public float volumeOpacity => m_LightVolumeIntensity;
|
||||
public float volumeIntensity => m_LightVolumeIntensity;
|
||||
|
||||
public bool volumeIntensityEnabled { get => m_LightVolumeIntensityEnabled; set => m_LightVolumeIntensityEnabled = value; }
|
||||
public Sprite lightCookieSprite { get { return m_LightType != LightType.Point ? m_LightCookieSprite : m_DeprecatedPointLightCookieSprite; } }
|
||||
public float falloffIntensity => m_FalloffIntensity;
|
||||
|
||||
[Obsolete]
|
||||
public bool alphaBlendOnOverlap { get { return m_OverlapOperation == OverlapOperation.AlphaBlend; }}
|
||||
public OverlapOperation overlapOperation => m_OverlapOperation;
|
||||
|
||||
public int lightOrder { get => m_LightOrder; set => m_LightOrder = value; }
|
||||
|
||||
public float normalMapDistance => m_NormalMapDistance;
|
||||
public NormalMapQuality normalMapQuality => m_NormalMapQuality;
|
||||
|
||||
|
||||
internal int GetTopMostLitLayer()
|
||||
{
|
||||
var largestIndex = Int32.MinValue;
|
||||
var largestLayer = 0;
|
||||
|
||||
var layers = Light2DManager.GetCachedSortingLayer();
|
||||
for (var i = 0; i < m_ApplyToSortingLayers.Length; ++i)
|
||||
{
|
||||
for (var layer = layers.Length - 1; layer >= largestLayer; --layer)
|
||||
{
|
||||
if (layers[layer].id == m_ApplyToSortingLayers[i])
|
||||
{
|
||||
largestIndex = layers[layer].value;
|
||||
largestLayer = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return largestIndex;
|
||||
}
|
||||
|
||||
internal void UpdateMesh(bool forceUpdate)
|
||||
{
|
||||
var shapePathHash = LightUtility.GetShapePathHash(shapePath);
|
||||
var fallOffSizeChanged = LightUtility.CheckForChange(m_ShapeLightFalloffSize, ref m_PreviousShapeLightFalloffSize);
|
||||
var parametricRadiusChanged = LightUtility.CheckForChange(m_ShapeLightParametricRadius, ref m_PreviousShapeLightParametricRadius);
|
||||
var parametricSidesChanged = LightUtility.CheckForChange(m_ShapeLightParametricSides, ref m_PreviousShapeLightParametricSides);
|
||||
var parametricAngleOffsetChanged = LightUtility.CheckForChange(m_ShapeLightParametricAngleOffset, ref m_PreviousShapeLightParametricAngleOffset);
|
||||
var spriteInstanceChanged = LightUtility.CheckForChange(lightCookieSpriteInstanceID, ref m_PreviousLightCookieSprite);
|
||||
var shapePathHashChanged = LightUtility.CheckForChange(shapePathHash, ref m_PreviousShapePathHash);
|
||||
var lightTypeChanged = LightUtility.CheckForChange(m_LightType, ref m_PreviousLightType);
|
||||
var hashChanged = fallOffSizeChanged || parametricRadiusChanged || parametricSidesChanged ||
|
||||
parametricAngleOffsetChanged || spriteInstanceChanged || shapePathHashChanged || lightTypeChanged;
|
||||
// Mesh Rebuilding
|
||||
if (hashChanged && forceUpdate)
|
||||
{
|
||||
switch (m_LightType)
|
||||
{
|
||||
case LightType.Freeform:
|
||||
m_LocalBounds = LightUtility.GenerateShapeMesh(this, m_ShapePath, m_ShapeLightFalloffSize);
|
||||
break;
|
||||
case LightType.Parametric:
|
||||
m_LocalBounds = LightUtility.GenerateParametricMesh(this, m_ShapeLightParametricRadius, m_ShapeLightFalloffSize, m_ShapeLightParametricAngleOffset, m_ShapeLightParametricSides);
|
||||
break;
|
||||
case LightType.Sprite:
|
||||
m_LocalBounds = LightUtility.GenerateSpriteMesh(this, m_LightCookieSprite);
|
||||
break;
|
||||
case LightType.Point:
|
||||
m_LocalBounds = LightUtility.GenerateParametricMesh(this, 1.412135f, 0, 0, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateBoundingSphere()
|
||||
{
|
||||
if (isPointLight)
|
||||
{
|
||||
boundingSphere = new BoundingSphere(transform.position, m_PointLightOuterRadius);
|
||||
return;
|
||||
}
|
||||
|
||||
var maxBound = transform.TransformPoint(Vector3.Max(m_LocalBounds.max, m_LocalBounds.max + (Vector3)m_ShapeLightFalloffOffset));
|
||||
var minBound = transform.TransformPoint(Vector3.Min(m_LocalBounds.min, m_LocalBounds.min + (Vector3)m_ShapeLightFalloffOffset));
|
||||
var center = 0.5f * (maxBound + minBound);
|
||||
var radius = Vector3.Magnitude(maxBound - center);
|
||||
|
||||
boundingSphere = new BoundingSphere(center, radius);
|
||||
}
|
||||
|
||||
internal bool IsLitLayer(int layer)
|
||||
{
|
||||
if (m_ApplyToSortingLayers == null)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < m_ApplyToSortingLayers.Length; i++)
|
||||
if (m_ApplyToSortingLayers[i] == layer)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!m_UseNormalMap && m_NormalMapQuality != NormalMapQuality.Disabled)
|
||||
m_NormalMapQuality = NormalMapQuality.Disabled;
|
||||
|
||||
UpdateMesh(!hasCachedMesh);
|
||||
if (hasCachedMesh)
|
||||
{
|
||||
lightMesh.SetVertexBufferParams(vertices.Length, LightUtility.LightMeshVertex.VertexLayout);
|
||||
lightMesh.SetVertexBufferData(vertices, 0, 0, vertices.Length);
|
||||
lightMesh.SetIndices(indices, MeshTopology.Triangles, 0, false);
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_PreviousLightCookieSprite = lightCookieSpriteInstanceID;
|
||||
Light2DManager.RegisterLight(this);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Light2DManager.DeregisterLight(this);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (m_LightType == LightType.Global)
|
||||
return;
|
||||
|
||||
UpdateMesh(true);
|
||||
UpdateBoundingSphere();
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
m_ComponentVersion = k_CurrentComponentVersion;
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
// Upgrade from no serialized version
|
||||
if (m_ComponentVersion == ComponentVersions.Version_Unserialized)
|
||||
{
|
||||
m_ShadowVolumeIntensityEnabled = m_ShadowVolumeIntensity > 0;
|
||||
m_ShadowIntensityEnabled = m_ShadowIntensity > 0;
|
||||
m_LightVolumeIntensityEnabled = m_LightVolumeIntensity > 0;
|
||||
|
||||
m_ComponentVersion = ComponentVersions.Version_1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
public sealed partial class Light2D
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
private const string s_IconsPath = "Packages/com.unity.render-pipelines.universal/Editor/2D/Resources/SceneViewIcons/";
|
||||
private static readonly string[] s_LightIconFileNames = new[]
|
||||
{
|
||||
"ParametricLight.png",
|
||||
"FreeformLight.png",
|
||||
"SpriteLight.png",
|
||||
"PointLight.png",
|
||||
"PointLight.png"
|
||||
};
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawIcon(transform.position, s_IconsPath + s_LightIconFileNames[(int)m_LightType], true);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_ShapePath = new Vector3[] { new Vector3(-0.5f, -0.5f), new Vector3(0.5f, -0.5f), new Vector3(0.5f, 0.5f), new Vector3(-0.5f, 0.5f) };
|
||||
}
|
||||
|
||||
internal List<Vector2> GetFalloffShape()
|
||||
{
|
||||
return LightUtility.GetOutlinePath(m_ShapePath, m_ShapeLightFalloffSize);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
[Serializable]
|
||||
[MovedFrom("UnityEngine.Experimental.Rendering.LWRP")] public struct Light2DBlendStyle
|
||||
{
|
||||
internal enum TextureChannel
|
||||
{
|
||||
None = 0,
|
||||
R = 1,
|
||||
G = 2,
|
||||
B = 3,
|
||||
A = 4,
|
||||
OneMinusR = 5,
|
||||
OneMinusG = 6,
|
||||
OneMinusB = 7,
|
||||
OneMinusA = 8
|
||||
}
|
||||
|
||||
internal struct MaskChannelFilter
|
||||
{
|
||||
public Vector4 mask { get; private set; }
|
||||
public Vector4 inverted { get; private set; }
|
||||
|
||||
public MaskChannelFilter(Vector4 m, Vector4 i)
|
||||
{
|
||||
mask = m;
|
||||
inverted = i;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum BlendMode
|
||||
{
|
||||
Additive = 0,
|
||||
Multiply = 1,
|
||||
Subtractive = 2
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct BlendFactors
|
||||
{
|
||||
public float multiplicative;
|
||||
public float additive;
|
||||
}
|
||||
|
||||
public string name;
|
||||
|
||||
[SerializeField]
|
||||
internal TextureChannel maskTextureChannel;
|
||||
|
||||
[SerializeField]
|
||||
internal BlendMode blendMode;
|
||||
|
||||
internal Vector2 blendFactors
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new Vector2();
|
||||
|
||||
switch (blendMode)
|
||||
{
|
||||
case BlendMode.Additive:
|
||||
result.x = 0.0f;
|
||||
result.y = 1.0f;
|
||||
break;
|
||||
case BlendMode.Multiply:
|
||||
result.x = 1.0f;
|
||||
result.y = 0.0f;
|
||||
break;
|
||||
case BlendMode.Subtractive:
|
||||
result.x = 0.0f;
|
||||
result.y = -1.0f;
|
||||
break;
|
||||
default:
|
||||
result.x = 1.0f;
|
||||
result.y = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal MaskChannelFilter maskTextureChannelFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (maskTextureChannel)
|
||||
{
|
||||
case TextureChannel.R:
|
||||
return new MaskChannelFilter(new Vector4(1, 0, 0, 0), new Vector4(0, 0, 0, 0));
|
||||
case TextureChannel.OneMinusR:
|
||||
return new MaskChannelFilter(new Vector4(1, 0, 0, 0), new Vector4(1, 0, 0, 0));
|
||||
case TextureChannel.G:
|
||||
return new MaskChannelFilter(new Vector4(0, 1, 0, 0), new Vector4(0, 0, 0, 0));
|
||||
case TextureChannel.OneMinusG:
|
||||
return new MaskChannelFilter(new Vector4(0, 1, 0, 0), new Vector4(0, 1, 0, 0));
|
||||
case TextureChannel.B:
|
||||
return new MaskChannelFilter(new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 0));
|
||||
case TextureChannel.OneMinusB:
|
||||
return new MaskChannelFilter(new Vector4(0, 0, 1, 0), new Vector4(0, 0, 1, 0));
|
||||
case TextureChannel.A:
|
||||
return new MaskChannelFilter(new Vector4(0, 0, 0, 1), new Vector4(0, 0, 0, 0));
|
||||
case TextureChannel.OneMinusA:
|
||||
return new MaskChannelFilter(new Vector4(0, 0, 0, 1), new Vector4(0, 0, 0, 1));
|
||||
case TextureChannel.None:
|
||||
default:
|
||||
return new MaskChannelFilter(Vector4.zero, Vector4.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transient data
|
||||
internal bool isDirty { get; set; }
|
||||
internal bool hasRenderTarget { get; set; }
|
||||
internal RenderTargetHandle renderTargetHandle;
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal struct LightStats
|
||||
{
|
||||
public int totalLights;
|
||||
public int totalNormalMapUsage;
|
||||
public int totalVolumetricUsage;
|
||||
public uint blendStylesUsed;
|
||||
public uint blendStylesWithLights;
|
||||
}
|
||||
|
||||
internal interface ILight2DCullResult
|
||||
{
|
||||
List<Light2D> visibleLights { get; }
|
||||
LightStats GetLightStatsByLayer(int layer);
|
||||
bool IsSceneLit();
|
||||
}
|
||||
|
||||
internal class Light2DCullResult : ILight2DCullResult
|
||||
{
|
||||
private List<Light2D> m_VisibleLights = new List<Light2D>();
|
||||
public List<Light2D> visibleLights => m_VisibleLights;
|
||||
|
||||
public bool IsSceneLit()
|
||||
{
|
||||
if (visibleLights.Count > 0)
|
||||
return true;
|
||||
|
||||
foreach (var light in Light2DManager.lights)
|
||||
{
|
||||
if (light.lightType == Light2D.LightType.Global)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public LightStats GetLightStatsByLayer(int layer)
|
||||
{
|
||||
var returnStats = new LightStats();
|
||||
foreach (var light in visibleLights)
|
||||
{
|
||||
if (!light.IsLitLayer(layer))
|
||||
continue;
|
||||
|
||||
returnStats.totalLights++;
|
||||
if (light.normalMapQuality != Light2D.NormalMapQuality.Disabled)
|
||||
returnStats.totalNormalMapUsage++;
|
||||
if (light.volumeIntensity > 0)
|
||||
returnStats.totalVolumetricUsage++;
|
||||
|
||||
returnStats.blendStylesUsed |= (uint)(1 << light.blendStyleIndex);
|
||||
if (light.lightType != Light2D.LightType.Global)
|
||||
returnStats.blendStylesWithLights |= (uint)(1 << light.blendStyleIndex);
|
||||
}
|
||||
|
||||
return returnStats;
|
||||
}
|
||||
|
||||
public void SetupCulling(ref ScriptableCullingParameters cullingParameters, Camera camera)
|
||||
{
|
||||
Profiler.BeginSample("Cull 2D Lights");
|
||||
m_VisibleLights.Clear();
|
||||
foreach (var light in Light2DManager.lights)
|
||||
{
|
||||
if ((camera.cullingMask & (1 << light.gameObject.layer)) == 0)
|
||||
continue;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(light.gameObject, camera))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
if (light.lightType == Light2D.LightType.Global)
|
||||
{
|
||||
m_VisibleLights.Add(light);
|
||||
continue;
|
||||
}
|
||||
|
||||
Profiler.BeginSample("Test Planes");
|
||||
var position = light.boundingSphere.position;
|
||||
var culled = false;
|
||||
for (var i = 0; i < cullingParameters.cullingPlaneCount; ++i)
|
||||
{
|
||||
var plane = cullingParameters.GetCullingPlane(i);
|
||||
// most of the time is spent getting world position
|
||||
var distance = math.dot(position, plane.normal) + plane.distance;
|
||||
if (distance < -light.boundingSphere.radius)
|
||||
{
|
||||
culled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Profiler.EndSample();
|
||||
if (culled)
|
||||
continue;
|
||||
|
||||
m_VisibleLights.Add(light);
|
||||
}
|
||||
|
||||
// must be sorted here because light order could change
|
||||
m_VisibleLights.Sort((l1, l2) => l1.lightOrder - l2.lightOrder);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal static class Light2DManager
|
||||
{
|
||||
private static SortingLayer[] s_SortingLayers;
|
||||
|
||||
public static List<Light2D> lights { get; } = new List<Light2D>();
|
||||
|
||||
// Called during OnEnable
|
||||
public static void RegisterLight(Light2D light)
|
||||
{
|
||||
Debug.Assert(!lights.Contains(light));
|
||||
lights.Add(light);
|
||||
ErrorIfDuplicateGlobalLight(light);
|
||||
}
|
||||
|
||||
// Called during OnEnable
|
||||
public static void DeregisterLight(Light2D light)
|
||||
{
|
||||
Debug.Assert(lights.Contains(light));
|
||||
lights.Remove(light);
|
||||
}
|
||||
|
||||
public static void ErrorIfDuplicateGlobalLight(Light2D light)
|
||||
{
|
||||
if (light.lightType != Light2D.LightType.Global)
|
||||
return;
|
||||
|
||||
foreach (var sortingLayer in light.affectedSortingLayers)
|
||||
{
|
||||
// should this really trigger at runtime?
|
||||
if (ContainsDuplicateGlobalLight(sortingLayer, light.blendStyleIndex))
|
||||
Debug.LogError("More than one global light on layer " + SortingLayer.IDToName(sortingLayer) + " for light blend style index " + light.blendStyleIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetGlobalColor(int sortingLayerIndex, int blendStyleIndex, out Color color)
|
||||
{
|
||||
var foundGlobalColor = false;
|
||||
color = Color.black;
|
||||
|
||||
// This should be rewritten to search only global lights
|
||||
foreach (var light in lights)
|
||||
{
|
||||
if (light.lightType != Light2D.LightType.Global ||
|
||||
light.blendStyleIndex != blendStyleIndex ||
|
||||
!light.IsLitLayer(sortingLayerIndex))
|
||||
continue;
|
||||
|
||||
var inCurrentPrefabStage = true;
|
||||
#if UNITY_EDITOR
|
||||
// If we found the first global light in our prefab stage
|
||||
inCurrentPrefabStage = PrefabStageUtility.GetCurrentPrefabStage()?.IsPartOfPrefabContents(light.gameObject) ?? true;
|
||||
#endif
|
||||
|
||||
if (inCurrentPrefabStage)
|
||||
{
|
||||
color = light.color * light.intensity;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!foundGlobalColor)
|
||||
{
|
||||
color = light.color * light.intensity;
|
||||
foundGlobalColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundGlobalColor;
|
||||
}
|
||||
|
||||
private static bool ContainsDuplicateGlobalLight(int sortingLayerIndex, int blendStyleIndex)
|
||||
{
|
||||
var globalLightCount = 0;
|
||||
|
||||
// This should be rewritten to search only global lights
|
||||
foreach (var light in lights)
|
||||
{
|
||||
if (light.lightType == Light2D.LightType.Global &&
|
||||
light.blendStyleIndex == blendStyleIndex &&
|
||||
light.IsLitLayer(sortingLayerIndex))
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// If we found the first global light in our prefab stage
|
||||
if (PrefabStageUtility.GetPrefabStage(light.gameObject) == PrefabStageUtility.GetCurrentPrefabStage())
|
||||
#endif
|
||||
{
|
||||
if (globalLightCount > 0)
|
||||
return true;
|
||||
|
||||
globalLightCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static SortingLayer[] GetCachedSortingLayer()
|
||||
{
|
||||
if (s_SortingLayers is null)
|
||||
{
|
||||
s_SortingLayers = SortingLayer.layers;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
// we should fix. Make a non allocating version of this
|
||||
if (!Application.isPlaying)
|
||||
s_SortingLayers = SortingLayer.layers;
|
||||
#endif
|
||||
return s_SortingLayers;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
public sealed partial class Light2D
|
||||
{
|
||||
[SerializeField] float m_PointLightInnerAngle = 360.0f;
|
||||
[SerializeField] float m_PointLightOuterAngle = 360.0f;
|
||||
[SerializeField] float m_PointLightInnerRadius = 0.0f;
|
||||
[SerializeField] float m_PointLightOuterRadius = 1.0f;
|
||||
|
||||
public float pointLightInnerAngle
|
||||
{
|
||||
get => m_PointLightInnerAngle;
|
||||
set => m_PointLightInnerAngle = value;
|
||||
}
|
||||
|
||||
public float pointLightOuterAngle
|
||||
{
|
||||
get => m_PointLightOuterAngle;
|
||||
set => m_PointLightOuterAngle = value;
|
||||
}
|
||||
|
||||
public float pointLightInnerRadius
|
||||
{
|
||||
get => m_PointLightInnerRadius;
|
||||
set => m_PointLightInnerRadius = value;
|
||||
}
|
||||
|
||||
public float pointLightOuterRadius
|
||||
{
|
||||
get => m_PointLightOuterRadius;
|
||||
set => m_PointLightOuterRadius = value;
|
||||
}
|
||||
|
||||
[Obsolete("pointLightDistance has been changed to normalMapDistance", true)]
|
||||
public float pointLightDistance => m_NormalMapDistance;
|
||||
|
||||
[Obsolete("pointLightQuality has been changed to normalMapQuality", true)]
|
||||
public NormalMapQuality pointLightQuality => m_NormalMapQuality;
|
||||
|
||||
|
||||
internal bool isPointLight => m_LightType == LightType.Point;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
public sealed partial class Light2D
|
||||
{
|
||||
[SerializeField] int m_ShapeLightParametricSides = 5;
|
||||
[SerializeField] float m_ShapeLightParametricAngleOffset = 0.0f;
|
||||
[SerializeField] float m_ShapeLightParametricRadius = 1.0f;
|
||||
[SerializeField] float m_ShapeLightFalloffSize = 0.50f;
|
||||
[SerializeField] Vector2 m_ShapeLightFalloffOffset = Vector2.zero;
|
||||
[SerializeField] Vector3[] m_ShapePath = null;
|
||||
|
||||
float m_PreviousShapeLightFalloffSize = -1;
|
||||
int m_PreviousShapeLightParametricSides = -1;
|
||||
float m_PreviousShapeLightParametricAngleOffset = -1;
|
||||
float m_PreviousShapeLightParametricRadius = -1;
|
||||
int m_PreviousShapePathHash = -1;
|
||||
LightType m_PreviousLightType = LightType.Parametric;
|
||||
|
||||
public int shapeLightParametricSides => m_ShapeLightParametricSides;
|
||||
public float shapeLightParametricAngleOffset => m_ShapeLightParametricAngleOffset;
|
||||
public float shapeLightParametricRadius => m_ShapeLightParametricRadius;
|
||||
public float shapeLightFalloffSize => m_ShapeLightFalloffSize;
|
||||
|
||||
public Vector3[] shapePath
|
||||
{
|
||||
get { return m_ShapePath; }
|
||||
internal set { m_ShapePath = value; }
|
||||
}
|
||||
|
||||
internal void SetShapePath(Vector3[] path)
|
||||
{
|
||||
m_ShapePath = path;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.LWRP
|
||||
{
|
||||
[Obsolete("LWRP -> Universal (UnityUpgradable) -> UnityEngine.Experimental.Rendering.Universal.Light2D", true)]
|
||||
public class Light2D
|
||||
{}
|
||||
}
|
@@ -0,0 +1,522 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Experimental.Rendering.Universal.LibTessDotNet;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal static class LightUtility
|
||||
{
|
||||
public static bool CheckForChange(Light2D.LightType a, ref Light2D.LightType b)
|
||||
{
|
||||
var changed = a != b;
|
||||
b = a;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static bool CheckForChange(int a, ref int b)
|
||||
{
|
||||
var changed = a != b;
|
||||
b = a;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static bool CheckForChange(float a, ref float b)
|
||||
{
|
||||
var changed = a != b;
|
||||
b = a;
|
||||
return changed;
|
||||
}
|
||||
|
||||
public static bool CheckForChange(bool a, ref bool b)
|
||||
{
|
||||
var changed = a != b;
|
||||
b = a;
|
||||
return changed;
|
||||
}
|
||||
|
||||
private enum PivotType
|
||||
{
|
||||
PivotBase,
|
||||
PivotCurve,
|
||||
PivotIntersect,
|
||||
PivotSkip,
|
||||
PivotClip
|
||||
};
|
||||
|
||||
[Serializable]
|
||||
internal struct LightMeshVertex
|
||||
{
|
||||
public Vector3 position;
|
||||
public Color color;
|
||||
public Vector2 uv;
|
||||
|
||||
public static readonly VertexAttributeDescriptor[] VertexLayout = new[]
|
||||
{
|
||||
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
|
||||
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
|
||||
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
|
||||
};
|
||||
}
|
||||
|
||||
static void Tessellate(Tess tess, ElementType boundaryType, NativeArray<ushort> indices,
|
||||
NativeArray<LightMeshVertex> vertices, Color c, ref int VCount, ref int ICount)
|
||||
{
|
||||
tess.Tessellate(WindingRule.NonZero, boundaryType, 3);
|
||||
|
||||
var prevCount = VCount;
|
||||
var tessIndices = tess.Elements.Select(i => i);
|
||||
var tessVertices = tess.Vertices.Select(v =>
|
||||
new LightMeshVertex() { position = new float3(v.Position.X, v.Position.Y, 0), color = c });
|
||||
|
||||
foreach (var v in tessVertices)
|
||||
vertices[VCount++] = v;
|
||||
foreach (var i in tessIndices)
|
||||
indices[ICount++] = (ushort)(i + prevCount);
|
||||
}
|
||||
|
||||
static bool TestPivot(List<IntPoint> path, int activePoint, long lastPoint)
|
||||
{
|
||||
for (int i = activePoint; i < path.Count; ++i)
|
||||
{
|
||||
if (path[i].N > lastPoint)
|
||||
return true;
|
||||
}
|
||||
|
||||
return (path[activePoint].N == -1);
|
||||
}
|
||||
|
||||
// Degenerate Pivots at the End Points.
|
||||
static List<IntPoint> DegeneratePivots(List<IntPoint> path, List<IntPoint> inPath)
|
||||
{
|
||||
List<IntPoint> degenerate = new List<IntPoint>();
|
||||
var minN = path[0].N;
|
||||
var maxN = path[0].N;
|
||||
for (int i = 1; i < path.Count; ++i)
|
||||
{
|
||||
if (path[i].N != -1)
|
||||
{
|
||||
minN = Math.Min(minN, path[i].N);
|
||||
maxN = Math.Max(maxN, path[i].N);
|
||||
}
|
||||
}
|
||||
|
||||
for (long i = 0; i < minN; ++i)
|
||||
{
|
||||
IntPoint ins = path[(int)minN];
|
||||
ins.N = i;
|
||||
degenerate.Add(ins);
|
||||
}
|
||||
degenerate.AddRange(path.GetRange(0, path.Count));
|
||||
for (long i = maxN + 1; i < inPath.Count; ++i)
|
||||
{
|
||||
IntPoint ins = inPath[(int)i];
|
||||
ins.N = i;
|
||||
degenerate.Add(ins);
|
||||
}
|
||||
return degenerate;
|
||||
}
|
||||
|
||||
// Ensure that we get a valid path from 0.
|
||||
static List<IntPoint> SortPivots(List<IntPoint> outPath, List<IntPoint> inPath)
|
||||
{
|
||||
List<IntPoint> sorted = new List<IntPoint>();
|
||||
var min = outPath[0].N;
|
||||
var max = outPath[0].N;
|
||||
var minIndex = 0;
|
||||
bool newMin = true;
|
||||
for (int i = 1; i < outPath.Count; ++i)
|
||||
{
|
||||
if (max > outPath[i].N && newMin && outPath[i].N != -1)
|
||||
{
|
||||
min = max = outPath[i].N;
|
||||
minIndex = i;
|
||||
newMin = false;
|
||||
}
|
||||
else if (outPath[i].N >= max)
|
||||
{
|
||||
max = outPath[i].N;
|
||||
newMin = true;
|
||||
}
|
||||
}
|
||||
sorted.AddRange(outPath.GetRange(minIndex, (outPath.Count - minIndex)));
|
||||
sorted.AddRange(outPath.GetRange(0, minIndex));
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// Ensure that all points eliminated due to overlaps and intersections are accounted for Tessellation.
|
||||
static List<IntPoint> FixPivots(List<IntPoint> outPath, List<IntPoint> inPath)
|
||||
{
|
||||
var path = SortPivots(outPath, inPath);
|
||||
long pivotPoint = path[0].N;
|
||||
|
||||
// Connect Points for Overlaps.
|
||||
for (int i = 1; i < path.Count; ++i)
|
||||
{
|
||||
var j = (i == path.Count - 1) ? 0 : (i + 1);
|
||||
var prev = path[i - 1];
|
||||
var curr = path[i];
|
||||
var next = path[j];
|
||||
|
||||
if (prev.N > curr.N)
|
||||
{
|
||||
var incr = TestPivot(path, i, pivotPoint);
|
||||
if (incr)
|
||||
{
|
||||
if (prev.N == next.N)
|
||||
curr.N = prev.N;
|
||||
else
|
||||
curr.N = (pivotPoint + 1) < inPath.Count ? (pivotPoint + 1) : 0;
|
||||
curr.D = 3;
|
||||
path[i] = curr;
|
||||
}
|
||||
}
|
||||
pivotPoint = path[i].N;
|
||||
}
|
||||
|
||||
// Insert Skipped Points.
|
||||
for (int i = 1; i < path.Count - 1;)
|
||||
{
|
||||
var prev = path[i - 1];
|
||||
var curr = path[i];
|
||||
var next = path[i + 1];
|
||||
|
||||
if (curr.N - prev.N > 1)
|
||||
{
|
||||
if (curr.N == next.N)
|
||||
{
|
||||
IntPoint ins = curr;
|
||||
ins.N = (ins.N - 1);
|
||||
path[i] = ins;
|
||||
}
|
||||
else
|
||||
{
|
||||
IntPoint ins = curr;
|
||||
ins.N = (ins.N - 1);
|
||||
path.Insert(i, ins);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
path = DegeneratePivots(path, inPath);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Rough shape only used in Inspector for quick preview.
|
||||
internal static List<Vector2> GetOutlinePath(Vector3[] shapePath, float offsetDistance)
|
||||
{
|
||||
const float kClipperScale = 10000.0f;
|
||||
List<IntPoint> path = new List<IntPoint>();
|
||||
List<Vector2> output = new List<Vector2>();
|
||||
for (var i = 0; i < shapePath.Length; ++i)
|
||||
{
|
||||
var newPoint = new Vector2(shapePath[i].x, shapePath[i].y) * kClipperScale;
|
||||
path.Add(new IntPoint((System.Int64)(newPoint.x), (System.Int64)(newPoint.y)));
|
||||
}
|
||||
List<List<IntPoint>> solution = new List<List<IntPoint>>();
|
||||
ClipperOffset clipOffset = new ClipperOffset(2048.0f);
|
||||
clipOffset.AddPath(path, JoinType.jtRound, EndType.etClosedPolygon);
|
||||
clipOffset.Execute(ref solution, kClipperScale * offsetDistance, path.Count);
|
||||
if (solution.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < solution[0].Count; ++i)
|
||||
output.Add(new Vector2(solution[0][i].X / kClipperScale, solution[0][i].Y / kClipperScale));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void TransferToMesh(NativeArray<LightMeshVertex> vertices, int vertexCount, NativeArray<ushort> indices,
|
||||
int indexCount, Light2D light)
|
||||
{
|
||||
var mesh = light.lightMesh;
|
||||
mesh.SetVertexBufferParams(vertexCount, LightMeshVertex.VertexLayout);
|
||||
mesh.SetVertexBufferData(vertices, 0, 0, vertexCount);
|
||||
mesh.SetIndices(indices, 0, indexCount, MeshTopology.Triangles, 0, true);
|
||||
|
||||
light.vertices = new LightMeshVertex[vertexCount];
|
||||
NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertexCount);
|
||||
light.indices = new ushort[indexCount];
|
||||
NativeArray<ushort>.Copy(indices, light.indices, indexCount);
|
||||
}
|
||||
|
||||
public static Bounds GenerateShapeMesh(Light2D light, Vector3[] shapePath, float falloffDistance)
|
||||
{
|
||||
var ix = 0;
|
||||
var vcount = 0;
|
||||
var icount = 0;
|
||||
const float kClipperScale = 10000.0f;
|
||||
var mesh = light.lightMesh;
|
||||
|
||||
// todo Revisit this while we do Batching.
|
||||
var meshInteriorColor = new Color(0.0f, 0, 0, 1.0f);
|
||||
var meshExteriorColor = new Color(0.0f, 0, 0, 0.0f);
|
||||
var vertices = new NativeArray<LightMeshVertex>(shapePath.Length * 256, Allocator.Temp);
|
||||
var indices = new NativeArray<ushort>(shapePath.Length * 256, Allocator.Temp);
|
||||
|
||||
// Create shape geometry
|
||||
var inputPointCount = shapePath.Length;
|
||||
var inner = new ContourVertex[inputPointCount + 1];
|
||||
for (var i = 0; i < inputPointCount; ++i)
|
||||
inner[ix++] = new ContourVertex() { Position = new Vec3() { X = shapePath[i].x, Y = shapePath[i].y, Z = 0 } };
|
||||
inner[ix++] = inner[0];
|
||||
|
||||
var tess = new Tess();
|
||||
tess.AddContour(inner, ContourOrientation.CounterClockwise);
|
||||
Tessellate(tess, ElementType.Polygons, indices, vertices, meshInteriorColor, ref vcount, ref icount);
|
||||
|
||||
// Create falloff geometry
|
||||
List<IntPoint> path = new List<IntPoint>();
|
||||
for (var i = 0; i < inputPointCount; ++i)
|
||||
{
|
||||
var newPoint = new Vector2(inner[i].Position.X, inner[i].Position.Y) * kClipperScale;
|
||||
var addPoint = new IntPoint((System.Int64)(newPoint.x), (System.Int64)(newPoint.y));
|
||||
addPoint.N = i; addPoint.D = -1;
|
||||
path.Add(addPoint);
|
||||
}
|
||||
var lastPointIndex = inputPointCount - 1;
|
||||
|
||||
// Generate Bevels.
|
||||
List<List<IntPoint>> solution = new List<List<IntPoint>>();
|
||||
ClipperOffset clipOffset = new ClipperOffset(24.0f);
|
||||
clipOffset.AddPath(path, JoinType.jtRound, EndType.etClosedPolygon);
|
||||
clipOffset.Execute(ref solution, kClipperScale * falloffDistance, path.Count);
|
||||
|
||||
if (solution.Count > 0)
|
||||
{
|
||||
// Fix path for Pivots.
|
||||
var outPath = solution[0];
|
||||
var minPath = (long)inputPointCount;
|
||||
for (int i = 0; i < outPath.Count; ++i)
|
||||
minPath = (outPath[i].N != -1) ? Math.Min(minPath, outPath[i].N) : minPath;
|
||||
var containsStart = minPath == 0;
|
||||
outPath = FixPivots(outPath, path);
|
||||
|
||||
// Tessellate.
|
||||
var bIndices = new NativeArray<ushort>(icount + (outPath.Count * 6) + 6, Allocator.Temp);
|
||||
for (int i = 0; i < icount; ++i)
|
||||
bIndices[i] = indices[i];
|
||||
var bVertices = new NativeArray<LightMeshVertex>(vcount + outPath.Count + inputPointCount, Allocator.Temp);
|
||||
for (int i = 0; i < vcount; ++i)
|
||||
bVertices[i] = vertices[i];
|
||||
|
||||
var innerIndices = new ushort[inputPointCount];
|
||||
|
||||
// Inner Vertices. (These may or may not be part of the created path. Beware!!)
|
||||
for (int i = 0; i < inputPointCount; ++i)
|
||||
{
|
||||
bVertices[vcount++] = new LightMeshVertex()
|
||||
{
|
||||
position = new float3(inner[i].Position.X, inner[i].Position.Y, 0),
|
||||
color = meshInteriorColor
|
||||
};
|
||||
innerIndices[i] = (ushort)(vcount - 1);
|
||||
}
|
||||
|
||||
var saveIndex = (ushort)vcount;
|
||||
var pathStart = saveIndex;
|
||||
var prevIndex = outPath[0].N == -1 ? 0 : outPath[0].N;
|
||||
|
||||
for (int i = 0; i < outPath.Count; ++i)
|
||||
{
|
||||
var curr = outPath[i];
|
||||
var currPoint = new float2(curr.X / kClipperScale, curr.Y / kClipperScale);
|
||||
var currIndex = curr.N == -1 ? 0 : curr.N;
|
||||
|
||||
bVertices[vcount++] = new LightMeshVertex()
|
||||
{
|
||||
position = new float3(currPoint.x, currPoint.y, 0),
|
||||
color = meshExteriorColor
|
||||
};
|
||||
|
||||
if (prevIndex != currIndex)
|
||||
{
|
||||
bIndices[icount++] = innerIndices[prevIndex];
|
||||
bIndices[icount++] = innerIndices[currIndex];
|
||||
bIndices[icount++] = (ushort)(vcount - 1);
|
||||
}
|
||||
|
||||
bIndices[icount++] = innerIndices[prevIndex];
|
||||
bIndices[icount++] = saveIndex;
|
||||
bIndices[icount++] = saveIndex = (ushort)(vcount - 1);
|
||||
prevIndex = currIndex;
|
||||
}
|
||||
|
||||
// Close the Loop.
|
||||
{
|
||||
bIndices[icount++] = pathStart;
|
||||
bIndices[icount++] = innerIndices[minPath];
|
||||
bIndices[icount++] = containsStart ? innerIndices[lastPointIndex] : saveIndex;
|
||||
|
||||
bIndices[icount++] = containsStart ? pathStart : saveIndex;
|
||||
bIndices[icount++] = containsStart ? saveIndex : innerIndices[minPath];
|
||||
bIndices[icount++] = containsStart ? innerIndices[lastPointIndex] : innerIndices[minPath - 1];
|
||||
}
|
||||
|
||||
TransferToMesh(bVertices, vcount, bIndices, icount, light);
|
||||
}
|
||||
else
|
||||
{
|
||||
TransferToMesh(vertices, vcount, indices, icount, light);
|
||||
}
|
||||
|
||||
return mesh.GetSubMesh(0).bounds;
|
||||
}
|
||||
|
||||
public static Bounds GenerateParametricMesh(Light2D light, float radius, float falloffDistance, float angle, int sides)
|
||||
{
|
||||
var angleOffset = Mathf.PI / 2.0f + Mathf.Deg2Rad * angle;
|
||||
if (sides < 3)
|
||||
{
|
||||
radius = 0.70710678118654752440084436210485f * radius;
|
||||
sides = 4;
|
||||
}
|
||||
|
||||
if (sides == 4)
|
||||
{
|
||||
angleOffset = Mathf.PI / 4.0f + Mathf.Deg2Rad * angle;
|
||||
}
|
||||
|
||||
var vertexCount = 1 + 2 * sides;
|
||||
var indexCount = 3 * 3 * sides;
|
||||
var vertices = new NativeArray<LightMeshVertex>(vertexCount, Allocator.Temp);
|
||||
var triangles = new NativeArray<ushort>(indexCount, Allocator.Temp);
|
||||
var centerIndex = (ushort)(2 * sides);
|
||||
var mesh = light.lightMesh;
|
||||
|
||||
// Only Alpha value in Color channel is ever used. May remove it or keep it for batching params in the future.
|
||||
var color = new Color(0, 0, 0, 1);
|
||||
vertices[centerIndex] = new LightMeshVertex
|
||||
{
|
||||
position = float3.zero,
|
||||
color = color
|
||||
};
|
||||
|
||||
var radiansPerSide = 2 * Mathf.PI / sides;
|
||||
var min = new float3(float.MaxValue, float.MaxValue, 0);
|
||||
var max = new float3(float.MinValue, float.MinValue, 0);
|
||||
|
||||
for (var i = 0; i < sides; i++)
|
||||
{
|
||||
var endAngle = (i + 1) * radiansPerSide;
|
||||
var extrudeDir = new float3(math.cos(endAngle + angleOffset), math.sin(endAngle + angleOffset), 0);
|
||||
var endPoint = radius * extrudeDir;
|
||||
|
||||
var vertexIndex = (2 * i + 2) % (2 * sides);
|
||||
vertices[vertexIndex] = new LightMeshVertex
|
||||
{
|
||||
position = endPoint,
|
||||
color = new Color(extrudeDir.x, extrudeDir.y, 0, 0)
|
||||
};
|
||||
vertices[vertexIndex + 1] = new LightMeshVertex
|
||||
{
|
||||
position = endPoint,
|
||||
color = color
|
||||
};
|
||||
|
||||
// Triangle 1 (Tip)
|
||||
var triangleIndex = 9 * i;
|
||||
triangles[triangleIndex] = (ushort)(vertexIndex + 1);
|
||||
triangles[triangleIndex + 1] = (ushort)(2 * i + 1);
|
||||
triangles[triangleIndex + 2] = centerIndex;
|
||||
|
||||
// Triangle 2 (Upper Top Left)
|
||||
triangles[triangleIndex + 3] = (ushort)(vertexIndex);
|
||||
triangles[triangleIndex + 4] = (ushort)(2 * i);
|
||||
triangles[triangleIndex + 5] = (ushort)(2 * i + 1);
|
||||
|
||||
// Triangle 2 (Bottom Top Left)
|
||||
triangles[triangleIndex + 6] = (ushort)(vertexIndex + 1);
|
||||
triangles[triangleIndex + 7] = (ushort)(vertexIndex);
|
||||
triangles[triangleIndex + 8] = (ushort)(2 * i + 1);
|
||||
|
||||
min = math.min(min, endPoint + extrudeDir * falloffDistance);
|
||||
max = math.max(max, endPoint + extrudeDir * falloffDistance);
|
||||
}
|
||||
|
||||
mesh.SetVertexBufferParams(vertexCount, LightMeshVertex.VertexLayout);
|
||||
mesh.SetVertexBufferData(vertices, 0, 0, vertexCount);
|
||||
mesh.SetIndices(triangles, MeshTopology.Triangles, 0, false);
|
||||
|
||||
light.vertices = new LightMeshVertex[vertexCount];
|
||||
NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertexCount);
|
||||
light.indices = new ushort[indexCount];
|
||||
NativeArray<ushort>.Copy(triangles, light.indices, indexCount);
|
||||
|
||||
return new Bounds
|
||||
{
|
||||
min = min,
|
||||
max = max
|
||||
};
|
||||
}
|
||||
|
||||
public static Bounds GenerateSpriteMesh(Light2D light, Sprite sprite)
|
||||
{
|
||||
// this needs to be called before getting UV at the line below.
|
||||
// Venky fixed it, enroute to trunk
|
||||
var uvs = sprite.uv;
|
||||
var mesh = light.lightMesh;
|
||||
|
||||
if (sprite == null)
|
||||
{
|
||||
mesh.Clear();
|
||||
return new Bounds(Vector3.zero, Vector3.zero);
|
||||
}
|
||||
|
||||
var srcVertices = sprite.GetVertexAttribute<Vector3>(VertexAttribute.Position);
|
||||
var srcUVs = sprite.GetVertexAttribute<Vector2>(VertexAttribute.TexCoord0);
|
||||
var srcIndices = sprite.GetIndices();
|
||||
|
||||
var center = 0.5f * (sprite.bounds.min + sprite.bounds.max);
|
||||
var vertices = new NativeArray<LightMeshVertex>(srcIndices.Length, Allocator.Temp);
|
||||
var color = new Color(0, 0, 0, 1);
|
||||
|
||||
for (var i = 0; i < srcVertices.Length; i++)
|
||||
{
|
||||
vertices[i] = new LightMeshVertex
|
||||
{
|
||||
position = new Vector3(srcVertices[i].x, srcVertices[i].y, 0) - center,
|
||||
color = color,
|
||||
uv = srcUVs[i]
|
||||
};
|
||||
}
|
||||
mesh.SetVertexBufferParams(vertices.Length, LightMeshVertex.VertexLayout);
|
||||
mesh.SetVertexBufferData(vertices, 0, 0, vertices.Length);
|
||||
mesh.SetIndices(srcIndices, MeshTopology.Triangles, 0, true);
|
||||
|
||||
light.vertices = new LightMeshVertex[vertices.Length];
|
||||
NativeArray<LightMeshVertex>.Copy(vertices, light.vertices, vertices.Length);
|
||||
light.indices = new ushort[srcIndices.Length];
|
||||
NativeArray<ushort>.Copy(srcIndices, light.indices, srcIndices.Length);
|
||||
|
||||
return mesh.GetSubMesh(0).bounds;
|
||||
}
|
||||
|
||||
public static int GetShapePathHash(Vector3[] path)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = (int)2166136261;
|
||||
|
||||
if (path != null)
|
||||
{
|
||||
foreach (var point in path)
|
||||
hashCode = hashCode * 16777619 ^ point.GetHashCode();
|
||||
}
|
||||
else
|
||||
{
|
||||
hashCode = 0;
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal interface IRenderPass2D
|
||||
{
|
||||
Renderer2DData rendererData { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
// Only to be used when Pixel Perfect Camera is present and it has Crop Frame X or Y enabled.
|
||||
// This pass simply clears BuiltinRenderTextureType.CameraTarget to black, so that the letterbox or pillarbox is black instead of garbage.
|
||||
// In the future this can be extended to draw a custom background image instead of just clearing.
|
||||
internal class PixelPerfectBackgroundPass : ScriptableRenderPass
|
||||
{
|
||||
private static readonly ProfilingSampler m_ProfilingScope = new ProfilingSampler("Pixel Perfect Background Pass");
|
||||
|
||||
public PixelPerfectBackgroundPass(RenderPassEvent evt)
|
||||
{
|
||||
renderPassEvent = evt;
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
var cmd = CommandBufferPool.Get();
|
||||
|
||||
using (new ProfilingScope(cmd, m_ProfilingScope))
|
||||
{
|
||||
CoreUtils.SetRenderTarget(
|
||||
cmd,
|
||||
BuiltinRenderTextureType.CameraTarget,
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.Store,
|
||||
ClearFlag.Color,
|
||||
Color.black);
|
||||
}
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,328 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal class Render2DLightingPass : ScriptableRenderPass, IRenderPass2D
|
||||
{
|
||||
private static readonly int k_HDREmulationScaleID = Shader.PropertyToID("_HDREmulationScale");
|
||||
private static readonly int k_InverseHDREmulationScaleID = Shader.PropertyToID("_InverseHDREmulationScale");
|
||||
private static readonly int k_UseSceneLightingID = Shader.PropertyToID("_UseSceneLighting");
|
||||
private static readonly int k_RendererColorID = Shader.PropertyToID("_RendererColor");
|
||||
private static readonly int k_CameraSortingLayerTextureID = Shader.PropertyToID("_CameraSortingLayerTexture");
|
||||
|
||||
private static readonly int[] k_ShapeLightTextureIDs =
|
||||
{
|
||||
Shader.PropertyToID("_ShapeLightTexture0"),
|
||||
Shader.PropertyToID("_ShapeLightTexture1"),
|
||||
Shader.PropertyToID("_ShapeLightTexture2"),
|
||||
Shader.PropertyToID("_ShapeLightTexture3")
|
||||
};
|
||||
|
||||
private static readonly ShaderTagId k_CombinedRenderingPassNameOld = new ShaderTagId("Lightweight2D");
|
||||
private static readonly ShaderTagId k_CombinedRenderingPassName = new ShaderTagId("Universal2D");
|
||||
private static readonly ShaderTagId k_NormalsRenderingPassName = new ShaderTagId("NormalsRendering");
|
||||
private static readonly ShaderTagId k_LegacyPassName = new ShaderTagId("SRPDefaultUnlit");
|
||||
private static readonly List<ShaderTagId> k_ShaderTags = new List<ShaderTagId>() { k_LegacyPassName, k_CombinedRenderingPassName, k_CombinedRenderingPassNameOld };
|
||||
|
||||
private static readonly ProfilingSampler m_ProfilingDrawLights = new ProfilingSampler("Draw 2D Lights");
|
||||
private static readonly ProfilingSampler m_ProfilingDrawLightTextures = new ProfilingSampler("Draw 2D Lights Textures");
|
||||
private static readonly ProfilingSampler m_ProfilingDrawRenderers = new ProfilingSampler("Draw All Renderers");
|
||||
private static readonly ProfilingSampler m_ProfilingDrawLayerBatch = new ProfilingSampler("Draw Layer Batch");
|
||||
private static readonly ProfilingSampler m_ProfilingSamplerUnlit = new ProfilingSampler("Render Unlit");
|
||||
|
||||
Material m_BlitMaterial;
|
||||
Material m_SamplingMaterial;
|
||||
|
||||
private readonly Renderer2DData m_Renderer2DData;
|
||||
|
||||
private bool m_HasValidDepth;
|
||||
|
||||
public Render2DLightingPass(Renderer2DData rendererData, Material blitMaterial, Material samplingMaterial)
|
||||
{
|
||||
m_Renderer2DData = rendererData;
|
||||
m_BlitMaterial = blitMaterial;
|
||||
m_SamplingMaterial = samplingMaterial;
|
||||
}
|
||||
|
||||
internal void Setup(bool hasValidDepth)
|
||||
{
|
||||
m_HasValidDepth = hasValidDepth;
|
||||
}
|
||||
|
||||
private void GetTransparencySortingMode(Camera camera, ref SortingSettings sortingSettings)
|
||||
{
|
||||
var mode = m_Renderer2DData.transparencySortMode;
|
||||
|
||||
if (mode == TransparencySortMode.Default)
|
||||
{
|
||||
mode = camera.orthographic ? TransparencySortMode.Orthographic : TransparencySortMode.Perspective;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TransparencySortMode.Perspective:
|
||||
sortingSettings.distanceMetric = DistanceMetric.Perspective;
|
||||
break;
|
||||
case TransparencySortMode.Orthographic:
|
||||
sortingSettings.distanceMetric = DistanceMetric.Orthographic;
|
||||
break;
|
||||
default:
|
||||
sortingSettings.distanceMetric = DistanceMetric.CustomAxis;
|
||||
sortingSettings.customAxis = m_Renderer2DData.transparencySortAxis;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyCameraSortingLayerRenderTexture(ScriptableRenderContext context, RenderingData renderingData)
|
||||
{
|
||||
var cmd = CommandBufferPool.Get();
|
||||
cmd.Clear();
|
||||
this.CreateCameraSortingLayerRenderTexture(renderingData, cmd, m_Renderer2DData.cameraSortingLayerDownsamplingMethod);
|
||||
|
||||
Material copyMaterial = m_Renderer2DData.cameraSortingLayerDownsamplingMethod == Downsampling._4xBox ? m_SamplingMaterial : m_BlitMaterial;
|
||||
RenderingUtils.Blit(cmd, colorAttachment, m_Renderer2DData.cameraSortingLayerRenderTarget.id, copyMaterial, 0, false, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare);
|
||||
cmd.SetRenderTarget(colorAttachment);
|
||||
cmd.SetGlobalTexture(k_CameraSortingLayerTextureID, m_Renderer2DData.cameraSortingLayerRenderTarget.id);
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
|
||||
private short GetCameraSortingLayerBoundsIndex()
|
||||
{
|
||||
SortingLayer[] sortingLayers = Light2DManager.GetCachedSortingLayer();
|
||||
for (short i = 0; i < sortingLayers.Length; i++)
|
||||
{
|
||||
if (sortingLayers[i].id == m_Renderer2DData.cameraSortingLayerTextureBound)
|
||||
return (short)sortingLayers[i].value;
|
||||
}
|
||||
|
||||
return short.MinValue;
|
||||
}
|
||||
|
||||
private int DrawLayerBatches(
|
||||
LayerBatch[] layerBatches,
|
||||
int batchCount,
|
||||
int startIndex,
|
||||
CommandBuffer cmd,
|
||||
ScriptableRenderContext context,
|
||||
ref RenderingData renderingData,
|
||||
ref FilteringSettings filterSettings,
|
||||
ref DrawingSettings normalsDrawSettings,
|
||||
ref DrawingSettings drawSettings,
|
||||
ref RenderTextureDescriptor desc)
|
||||
{
|
||||
var batchesDrawn = 0;
|
||||
var rtCount = 0U;
|
||||
// Draw lights
|
||||
using (new ProfilingScope(cmd, m_ProfilingDrawLights))
|
||||
{
|
||||
for (var i = startIndex; i < batchCount; ++i)
|
||||
{
|
||||
ref var layerBatch = ref layerBatches[i];
|
||||
|
||||
var blendStyleMask = layerBatch.lightStats.blendStylesUsed;
|
||||
var blendStyleCount = 0U;
|
||||
while (blendStyleMask > 0)
|
||||
{
|
||||
blendStyleCount += blendStyleMask & 1;
|
||||
blendStyleMask >>= 1;
|
||||
}
|
||||
|
||||
rtCount += blendStyleCount;
|
||||
|
||||
if (rtCount > LayerUtility.maxTextureCount)
|
||||
break;
|
||||
|
||||
batchesDrawn++;
|
||||
|
||||
if (layerBatch.lightStats.totalNormalMapUsage > 0)
|
||||
{
|
||||
filterSettings.sortingLayerRange = layerBatch.layerRange;
|
||||
var depthTarget = m_HasValidDepth ? depthAttachment : BuiltinRenderTextureType.None;
|
||||
this.RenderNormals(context, renderingData, normalsDrawSettings, filterSettings, depthTarget, cmd, layerBatch.lightStats);
|
||||
}
|
||||
|
||||
using (new ProfilingScope(cmd, m_ProfilingDrawLightTextures))
|
||||
{
|
||||
this.RenderLights(renderingData, cmd, layerBatch.startLayerID, ref layerBatch, ref desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw renderers
|
||||
var blendStylesCount = m_Renderer2DData.lightBlendStyles.Length;
|
||||
using (new ProfilingScope(cmd, m_ProfilingDrawRenderers))
|
||||
{
|
||||
cmd.SetRenderTarget(colorAttachment, depthAttachment);
|
||||
|
||||
for (var i = startIndex; i < startIndex + batchesDrawn; i++)
|
||||
{
|
||||
using (new ProfilingScope(cmd, m_ProfilingDrawLayerBatch))
|
||||
{
|
||||
// This is a local copy of the array element (it's a struct). Remember to add a ref here if you need to modify the real thing.
|
||||
var layerBatch = layerBatches[i];
|
||||
|
||||
if (layerBatch.lightStats.totalLights > 0)
|
||||
{
|
||||
for (var blendStyleIndex = 0; blendStyleIndex < blendStylesCount; blendStyleIndex++)
|
||||
{
|
||||
var blendStyleMask = (uint)(1 << blendStyleIndex);
|
||||
var blendStyleUsed = (layerBatch.lightStats.blendStylesUsed & blendStyleMask) > 0;
|
||||
|
||||
if (blendStyleUsed)
|
||||
{
|
||||
var identifier = layerBatch.GetRTId(cmd, desc, blendStyleIndex);
|
||||
cmd.SetGlobalTexture(k_ShapeLightTextureIDs[blendStyleIndex], identifier);
|
||||
}
|
||||
|
||||
RendererLighting.EnableBlendStyle(cmd, blendStyleIndex, blendStyleUsed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var blendStyleIndex = 0; blendStyleIndex < k_ShapeLightTextureIDs.Length; blendStyleIndex++)
|
||||
{
|
||||
cmd.SetGlobalTexture(k_ShapeLightTextureIDs[blendStyleIndex], Texture2D.blackTexture);
|
||||
RendererLighting.EnableBlendStyle(cmd, blendStyleIndex, blendStyleIndex == 0);
|
||||
}
|
||||
}
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
cmd.Clear();
|
||||
|
||||
|
||||
short cameraSortingLayerBoundsIndex = GetCameraSortingLayerBoundsIndex();
|
||||
// If our camera sorting layer texture bound is inside our batch we need to break up the DrawRenderers into two batches
|
||||
if (cameraSortingLayerBoundsIndex >= layerBatch.layerRange.lowerBound && cameraSortingLayerBoundsIndex < layerBatch.layerRange.upperBound && m_Renderer2DData.useCameraSortingLayerTexture)
|
||||
{
|
||||
filterSettings.sortingLayerRange = new SortingLayerRange(layerBatch.layerRange.lowerBound, cameraSortingLayerBoundsIndex);
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings);
|
||||
CopyCameraSortingLayerRenderTexture(context, renderingData);
|
||||
|
||||
filterSettings.sortingLayerRange = new SortingLayerRange((short)(cameraSortingLayerBoundsIndex + 1), layerBatch.layerRange.upperBound);
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
filterSettings.sortingLayerRange = new SortingLayerRange(layerBatch.layerRange.lowerBound, layerBatch.layerRange.upperBound);
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings);
|
||||
if (cameraSortingLayerBoundsIndex == layerBatch.layerRange.upperBound && m_Renderer2DData.useCameraSortingLayerTexture)
|
||||
CopyCameraSortingLayerRenderTexture(context, renderingData);
|
||||
}
|
||||
|
||||
// Draw light volumes
|
||||
if (layerBatch.lightStats.totalVolumetricUsage > 0)
|
||||
{
|
||||
var sampleName = "Render 2D Light Volumes";
|
||||
cmd.BeginSample(sampleName);
|
||||
|
||||
this.RenderLightVolumes(renderingData, cmd, layerBatch.startLayerID, layerBatch.endLayerValue, colorAttachment, depthAttachment, m_Renderer2DData.lightCullResult.visibleLights);
|
||||
|
||||
cmd.EndSample(sampleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = startIndex; i < startIndex + batchesDrawn; ++i)
|
||||
{
|
||||
ref var layerBatch = ref layerBatches[i];
|
||||
layerBatch.ReleaseRT(cmd);
|
||||
}
|
||||
|
||||
return batchesDrawn;
|
||||
}
|
||||
|
||||
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
var isLitView = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (renderingData.cameraData.isSceneViewCamera)
|
||||
isLitView = UnityEditor.SceneView.currentDrawingSceneView.sceneLighting;
|
||||
|
||||
if (renderingData.cameraData.camera.cameraType == CameraType.Preview)
|
||||
isLitView = false;
|
||||
#endif
|
||||
var camera = renderingData.cameraData.camera;
|
||||
var filterSettings = new FilteringSettings();
|
||||
filterSettings.renderQueueRange = RenderQueueRange.all;
|
||||
filterSettings.layerMask = -1;
|
||||
filterSettings.renderingLayerMask = 0xFFFFFFFF;
|
||||
filterSettings.sortingLayerRange = SortingLayerRange.all;
|
||||
|
||||
LayerUtility.InitializeBudget(m_Renderer2DData.lightRenderTextureMemoryBudget);
|
||||
ShadowRendering.InitializeBudget(m_Renderer2DData.shadowRenderTextureMemoryBudget);
|
||||
|
||||
var isSceneLit = m_Renderer2DData.lightCullResult.IsSceneLit();
|
||||
if (isSceneLit)
|
||||
{
|
||||
var combinedDrawSettings = CreateDrawingSettings(k_ShaderTags, ref renderingData, SortingCriteria.CommonTransparent);
|
||||
var normalsDrawSettings = CreateDrawingSettings(k_NormalsRenderingPassName, ref renderingData, SortingCriteria.CommonTransparent);
|
||||
|
||||
var sortSettings = combinedDrawSettings.sortingSettings;
|
||||
GetTransparencySortingMode(camera, ref sortSettings);
|
||||
combinedDrawSettings.sortingSettings = sortSettings;
|
||||
normalsDrawSettings.sortingSettings = sortSettings;
|
||||
|
||||
var cmd = CommandBufferPool.Get();
|
||||
cmd.SetGlobalFloat(k_HDREmulationScaleID, m_Renderer2DData.hdrEmulationScale);
|
||||
cmd.SetGlobalFloat(k_InverseHDREmulationScaleID, 1.0f / m_Renderer2DData.hdrEmulationScale);
|
||||
cmd.SetGlobalFloat(k_UseSceneLightingID, isLitView ? 1.0f : 0.0f);
|
||||
cmd.SetGlobalColor(k_RendererColorID, Color.white);
|
||||
this.SetShapeLightShaderGlobals(cmd);
|
||||
|
||||
var desc = this.GetBlendStyleRenderTextureDesc(renderingData);
|
||||
|
||||
var layerBatches = LayerUtility.CalculateBatches(m_Renderer2DData.lightCullResult, out var batchCount);
|
||||
var batchesDrawn = 0;
|
||||
|
||||
for (var i = 0; i < batchCount; i += batchesDrawn)
|
||||
batchesDrawn = DrawLayerBatches(layerBatches, batchCount, i, cmd, context, ref renderingData, ref filterSettings, ref normalsDrawSettings, ref combinedDrawSettings, ref desc);
|
||||
|
||||
this.ReleaseRenderTextures(cmd);
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
var unlitDrawSettings = CreateDrawingSettings(k_ShaderTags, ref renderingData, SortingCriteria.CommonTransparent);
|
||||
|
||||
var cmd = CommandBufferPool.Get();
|
||||
using (new ProfilingScope(cmd, m_ProfilingSamplerUnlit))
|
||||
{
|
||||
CoreUtils.SetRenderTarget(cmd, colorAttachment, depthAttachment, ClearFlag.None, Color.white);
|
||||
|
||||
cmd.SetGlobalFloat(k_UseSceneLightingID, isLitView ? 1.0f : 0.0f);
|
||||
cmd.SetGlobalColor(k_RendererColorID, Color.white);
|
||||
|
||||
for (var blendStyleIndex = 0; blendStyleIndex < k_ShapeLightTextureIDs.Length; blendStyleIndex++)
|
||||
{
|
||||
if (blendStyleIndex == 0)
|
||||
cmd.SetGlobalTexture(k_ShapeLightTextureIDs[blendStyleIndex], Texture2D.blackTexture);
|
||||
|
||||
RendererLighting.EnableBlendStyle(cmd, blendStyleIndex, blendStyleIndex == 0);
|
||||
}
|
||||
}
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
|
||||
Profiler.BeginSample("Render Sprites Unlit");
|
||||
context.DrawRenderers(renderingData.cullResults, ref unlitDrawSettings, ref filterSettings);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
filterSettings.sortingLayerRange = SortingLayerRange.all;
|
||||
RenderingUtils.RenderObjectsWithError(context, ref renderingData.cullResults, camera, filterSettings, SortingCriteria.None);
|
||||
}
|
||||
|
||||
Renderer2DData IRenderPass2D.rendererData
|
||||
{
|
||||
get { return m_Renderer2DData; }
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,154 @@
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal struct LayerBatch
|
||||
{
|
||||
public int startLayerID;
|
||||
public int endLayerValue;
|
||||
public SortingLayerRange layerRange;
|
||||
public LightStats lightStats;
|
||||
private unsafe fixed int renderTargetIds[4];
|
||||
private unsafe fixed bool renderTargetUsed[4];
|
||||
|
||||
public void InitRTIds(int index)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
renderTargetUsed[i] = false;
|
||||
renderTargetIds[i] = Shader.PropertyToID($"_LightTexture_{index}_{i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public RenderTargetIdentifier GetRTId(CommandBuffer cmd, RenderTextureDescriptor desc, int index)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (!renderTargetUsed[index])
|
||||
{
|
||||
cmd.GetTemporaryRT(renderTargetIds[index], desc, FilterMode.Bilinear);
|
||||
renderTargetUsed[index] = true;
|
||||
}
|
||||
return new RenderTargetIdentifier(renderTargetIds[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseRT(CommandBuffer cmd)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (!renderTargetUsed[i])
|
||||
continue;
|
||||
|
||||
cmd.ReleaseTemporaryRT(renderTargetIds[i]);
|
||||
renderTargetUsed[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class LayerUtility
|
||||
{
|
||||
private static LayerBatch[] s_LayerBatches;
|
||||
public static uint maxTextureCount { get; private set; }
|
||||
|
||||
public static void InitializeBudget(uint maxTextureCount)
|
||||
{
|
||||
LayerUtility.maxTextureCount = math.max(4, maxTextureCount);
|
||||
}
|
||||
|
||||
private static bool CanBatchLightsInLayer(int layerIndex1, int layerIndex2, SortingLayer[] sortingLayers, ILight2DCullResult lightCullResult)
|
||||
{
|
||||
var layerId1 = sortingLayers[layerIndex1].id;
|
||||
var layerId2 = sortingLayers[layerIndex2].id;
|
||||
foreach (var light in lightCullResult.visibleLights)
|
||||
{
|
||||
// If the lit layers are different, or if they are lit but this is a shadow casting light then don't batch.
|
||||
if ((light.IsLitLayer(layerId1) != light.IsLitLayer(layerId2)) || (light.IsLitLayer(layerId1) && light.shadowsEnabled))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int FindUpperBoundInBatch(int startLayerIndex, SortingLayer[] sortingLayers, ILight2DCullResult lightCullResult)
|
||||
{
|
||||
// start checking at the next layer
|
||||
for (var i = startLayerIndex + 1; i < sortingLayers.Length; i++)
|
||||
{
|
||||
if (!CanBatchLightsInLayer(startLayerIndex, i, sortingLayers, lightCullResult))
|
||||
return i - 1;
|
||||
}
|
||||
return sortingLayers.Length - 1;
|
||||
}
|
||||
|
||||
private static void InitializeBatchInfos(SortingLayer[] cachedSortingLayers)
|
||||
{
|
||||
var count = cachedSortingLayers.Length;
|
||||
var needInit = s_LayerBatches == null;
|
||||
if (s_LayerBatches is null)
|
||||
{
|
||||
s_LayerBatches = new LayerBatch[count];
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// we should fix. Make a non allocating version of this
|
||||
if (!Application.isPlaying && s_LayerBatches.Length != count)
|
||||
{
|
||||
s_LayerBatches = new LayerBatch[count];
|
||||
needInit = true;
|
||||
}
|
||||
#endif
|
||||
if (needInit)
|
||||
{
|
||||
for (var i = 0; i < s_LayerBatches.Length; i++)
|
||||
{
|
||||
ref var layerBatch = ref s_LayerBatches[i];
|
||||
layerBatch.InitRTIds(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LayerBatch[] CalculateBatches(ILight2DCullResult lightCullResult, out int batchCount)
|
||||
{
|
||||
var cachedSortingLayers = Light2DManager.GetCachedSortingLayer();
|
||||
InitializeBatchInfos(cachedSortingLayers);
|
||||
|
||||
batchCount = 0;
|
||||
for (var i = 0; i < cachedSortingLayers.Length;)
|
||||
{
|
||||
var layerToRender = cachedSortingLayers[i].id;
|
||||
var lightStats = lightCullResult.GetLightStatsByLayer(layerToRender);
|
||||
ref var layerBatch = ref s_LayerBatches[batchCount++];
|
||||
|
||||
// Find the highest layer that share the same set of lights as this layer.
|
||||
var upperLayerInBatch = FindUpperBoundInBatch(i, cachedSortingLayers, lightCullResult);
|
||||
|
||||
// Some renderers override their sorting layer value with short.MinValue or short.MaxValue.
|
||||
// When drawing the first sorting layer, we should include the range from short.MinValue to layerValue.
|
||||
// Similarly, when drawing the last sorting layer, include the range from layerValue to short.MaxValue.
|
||||
var startLayerValue = (short)cachedSortingLayers[i].value;
|
||||
var lowerBound = (i == 0) ? short.MinValue : startLayerValue;
|
||||
var endLayerValue = (short)cachedSortingLayers[upperLayerInBatch].value;
|
||||
var upperBound = (upperLayerInBatch == cachedSortingLayers.Length - 1) ? short.MaxValue : endLayerValue;
|
||||
|
||||
// Renderer within this range share the same set of lights so they should be rendered together.
|
||||
var sortingLayerRange = new SortingLayerRange(lowerBound, upperBound);
|
||||
|
||||
layerBatch.startLayerID = layerToRender;
|
||||
layerBatch.endLayerValue = endLayerValue;
|
||||
layerBatch.layerRange = sortingLayerRange;
|
||||
layerBatch.lightStats = lightStats;
|
||||
|
||||
i = upperLayerInBatch + 1;
|
||||
}
|
||||
|
||||
return s_LayerBatches;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal static class Light2DLookupTexture
|
||||
{
|
||||
private static Texture2D s_PointLightLookupTexture;
|
||||
private static Texture2D s_FalloffLookupTexture;
|
||||
|
||||
public static Texture GetLightLookupTexture()
|
||||
{
|
||||
if (s_PointLightLookupTexture == null)
|
||||
s_PointLightLookupTexture = CreatePointLightLookupTexture();
|
||||
return s_PointLightLookupTexture;
|
||||
}
|
||||
|
||||
private static Texture2D CreatePointLightLookupTexture()
|
||||
{
|
||||
const int WIDTH = 256;
|
||||
const int HEIGHT = 256;
|
||||
|
||||
var textureFormat = GraphicsFormat.R8G8B8A8_UNorm;
|
||||
if (RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.SetPixels))
|
||||
textureFormat = GraphicsFormat.R16G16B16A16_SFloat;
|
||||
else if (RenderingUtils.SupportsGraphicsFormat(GraphicsFormat.R32G32B32A32_SFloat, FormatUsage.SetPixels))
|
||||
textureFormat = GraphicsFormat.R32G32B32A32_SFloat;
|
||||
|
||||
var texture = new Texture2D(WIDTH, HEIGHT, textureFormat, TextureCreationFlags.None);
|
||||
texture.filterMode = FilterMode.Bilinear;
|
||||
texture.wrapMode = TextureWrapMode.Clamp;
|
||||
var center = new Vector2(WIDTH / 2.0f, HEIGHT / 2.0f);
|
||||
|
||||
for (var y = 0; y < HEIGHT; y++)
|
||||
{
|
||||
for (var x = 0; x < WIDTH; x++)
|
||||
{
|
||||
var pos = new Vector2(x, y);
|
||||
var distance = Vector2.Distance(pos, center);
|
||||
var relPos = pos - center;
|
||||
var direction = center - pos;
|
||||
direction.Normalize();
|
||||
|
||||
// red = 1-0 distance
|
||||
// green = 1-0 angle
|
||||
// blue = direction.x
|
||||
// alpha = direction.y
|
||||
|
||||
float red;
|
||||
if (x == WIDTH - 1 || y == HEIGHT - 1)
|
||||
red = 0;
|
||||
else
|
||||
red = Mathf.Clamp(1 - (2.0f * distance / WIDTH), 0.0f, 1.0f);
|
||||
|
||||
var cosAngle = Vector2.Dot(Vector2.down, relPos.normalized);
|
||||
var angle = Mathf.Acos(cosAngle) / Mathf.PI; // 0-1
|
||||
|
||||
var green = Mathf.Clamp(1 - angle, 0.0f, 1.0f);
|
||||
var blue = direction.x;
|
||||
var alpha = direction.y;
|
||||
|
||||
var color = new Color(red, green, blue, alpha);
|
||||
|
||||
texture.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
texture.Apply();
|
||||
return texture;
|
||||
}
|
||||
|
||||
//#if UNITY_EDITOR
|
||||
// [MenuItem("Light2D Debugging/Write Light Texture")]
|
||||
// static public void WriteLightTexture()
|
||||
// {
|
||||
// var path = EditorUtility.SaveFilePanel("Save texture as PNG", "", "LightLookupTexture.exr", "png");
|
||||
|
||||
// CreatePointLightLookupTexture();
|
||||
|
||||
// byte[] imgData = s_PointLightLookupTexture.EncodeToEXR(Texture2D.EXRFlags.CompressRLE);
|
||||
// if (imgData != null)
|
||||
// File.WriteAllBytes(path, imgData);
|
||||
// }
|
||||
|
||||
// [MenuItem("Light2D Debugging/Write Falloff Texture")]
|
||||
// static public void WriteCurveTexture()
|
||||
// {
|
||||
// var path = EditorUtility.SaveFilePanel("Save texture as PNG", "", "FalloffLookupTexture.png", "png");
|
||||
|
||||
// CreateFalloffLookupTexture();
|
||||
|
||||
// byte[] imgData = s_FalloffLookupTexture.EncodeToPNG();
|
||||
// if (imgData != null)
|
||||
// File.WriteAllBytes(path, imgData);
|
||||
// }
|
||||
//#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,615 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal static class RendererLighting
|
||||
{
|
||||
private static readonly ProfilingSampler m_ProfilingSampler = new ProfilingSampler("Draw Normals");
|
||||
private static readonly ShaderTagId k_NormalsRenderingPassName = new ShaderTagId("NormalsRendering");
|
||||
private static readonly Color k_NormalClearColor = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
private static readonly string k_SpriteLightKeyword = "SPRITE_LIGHT";
|
||||
private static readonly string k_UsePointLightCookiesKeyword = "USE_POINT_LIGHT_COOKIES";
|
||||
private static readonly string k_LightQualityFastKeyword = "LIGHT_QUALITY_FAST";
|
||||
private static readonly string k_UseNormalMap = "USE_NORMAL_MAP";
|
||||
private static readonly string k_UseAdditiveBlendingKeyword = "USE_ADDITIVE_BLENDING";
|
||||
|
||||
private static readonly string[] k_UseBlendStyleKeywords =
|
||||
{
|
||||
"USE_SHAPE_LIGHT_TYPE_0", "USE_SHAPE_LIGHT_TYPE_1", "USE_SHAPE_LIGHT_TYPE_2", "USE_SHAPE_LIGHT_TYPE_3"
|
||||
};
|
||||
|
||||
private static readonly int[] k_BlendFactorsPropIDs =
|
||||
{
|
||||
Shader.PropertyToID("_ShapeLightBlendFactors0"),
|
||||
Shader.PropertyToID("_ShapeLightBlendFactors1"),
|
||||
Shader.PropertyToID("_ShapeLightBlendFactors2"),
|
||||
Shader.PropertyToID("_ShapeLightBlendFactors3")
|
||||
};
|
||||
|
||||
private static readonly int[] k_MaskFilterPropIDs =
|
||||
{
|
||||
Shader.PropertyToID("_ShapeLightMaskFilter0"),
|
||||
Shader.PropertyToID("_ShapeLightMaskFilter1"),
|
||||
Shader.PropertyToID("_ShapeLightMaskFilter2"),
|
||||
Shader.PropertyToID("_ShapeLightMaskFilter3")
|
||||
};
|
||||
|
||||
private static readonly int[] k_InvertedFilterPropIDs =
|
||||
{
|
||||
Shader.PropertyToID("_ShapeLightInvertedFilter0"),
|
||||
Shader.PropertyToID("_ShapeLightInvertedFilter1"),
|
||||
Shader.PropertyToID("_ShapeLightInvertedFilter2"),
|
||||
Shader.PropertyToID("_ShapeLightInvertedFilter3")
|
||||
};
|
||||
|
||||
private static GraphicsFormat s_RenderTextureFormatToUse = GraphicsFormat.R8G8B8A8_UNorm;
|
||||
private static bool s_HasSetupRenderTextureFormatToUse;
|
||||
|
||||
private static readonly int k_SrcBlendID = Shader.PropertyToID("_SrcBlend");
|
||||
private static readonly int k_DstBlendID = Shader.PropertyToID("_DstBlend");
|
||||
private static readonly int k_FalloffIntensityID = Shader.PropertyToID("_FalloffIntensity");
|
||||
private static readonly int k_FalloffDistanceID = Shader.PropertyToID("_FalloffDistance");
|
||||
private static readonly int k_LightColorID = Shader.PropertyToID("_LightColor");
|
||||
private static readonly int k_VolumeOpacityID = Shader.PropertyToID("_VolumeOpacity");
|
||||
private static readonly int k_CookieTexID = Shader.PropertyToID("_CookieTex");
|
||||
private static readonly int k_FalloffLookupID = Shader.PropertyToID("_FalloffLookup");
|
||||
private static readonly int k_LightPositionID = Shader.PropertyToID("_LightPosition");
|
||||
private static readonly int k_LightInvMatrixID = Shader.PropertyToID("_LightInvMatrix");
|
||||
private static readonly int k_InnerRadiusMultID = Shader.PropertyToID("_InnerRadiusMult");
|
||||
private static readonly int k_OuterAngleID = Shader.PropertyToID("_OuterAngle");
|
||||
private static readonly int k_InnerAngleMultID = Shader.PropertyToID("_InnerAngleMult");
|
||||
private static readonly int k_LightLookupID = Shader.PropertyToID("_LightLookup");
|
||||
private static readonly int k_IsFullSpotlightID = Shader.PropertyToID("_IsFullSpotlight");
|
||||
private static readonly int k_LightZDistanceID = Shader.PropertyToID("_LightZDistance");
|
||||
private static readonly int k_PointLightCookieTexID = Shader.PropertyToID("_PointLightCookieTex");
|
||||
|
||||
private static GraphicsFormat GetRenderTextureFormat()
|
||||
{
|
||||
if (!s_HasSetupRenderTextureFormatToUse)
|
||||
{
|
||||
if (SystemInfo.IsFormatSupported(GraphicsFormat.B10G11R11_UFloatPack32, FormatUsage.Linear | FormatUsage.Render))
|
||||
s_RenderTextureFormatToUse = GraphicsFormat.B10G11R11_UFloatPack32;
|
||||
else if (SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.Linear | FormatUsage.Render))
|
||||
s_RenderTextureFormatToUse = GraphicsFormat.R16G16B16A16_SFloat;
|
||||
|
||||
s_HasSetupRenderTextureFormatToUse = true;
|
||||
}
|
||||
|
||||
return s_RenderTextureFormatToUse;
|
||||
}
|
||||
|
||||
public static void CreateNormalMapRenderTexture(this IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmd, float renderScale)
|
||||
{
|
||||
if (renderScale != pass.rendererData.normalsRenderTargetScale)
|
||||
{
|
||||
if (pass.rendererData.isNormalsRenderTargetValid)
|
||||
{
|
||||
cmd.ReleaseTemporaryRT(pass.rendererData.normalsRenderTarget.id);
|
||||
}
|
||||
|
||||
pass.rendererData.isNormalsRenderTargetValid = true;
|
||||
pass.rendererData.normalsRenderTargetScale = renderScale;
|
||||
|
||||
var descriptor = new RenderTextureDescriptor(
|
||||
(int)(renderingData.cameraData.cameraTargetDescriptor.width * renderScale),
|
||||
(int)(renderingData.cameraData.cameraTargetDescriptor.height * renderScale));
|
||||
|
||||
descriptor.graphicsFormat = GetRenderTextureFormat();
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.msaaSamples = renderingData.cameraData.cameraTargetDescriptor.msaaSamples;
|
||||
descriptor.dimension = TextureDimension.Tex2D;
|
||||
|
||||
cmd.GetTemporaryRT(pass.rendererData.normalsRenderTarget.id, descriptor, FilterMode.Bilinear);
|
||||
}
|
||||
}
|
||||
|
||||
public static RenderTextureDescriptor GetBlendStyleRenderTextureDesc(this IRenderPass2D pass, RenderingData renderingData)
|
||||
{
|
||||
var renderTextureScale = Mathf.Clamp(pass.rendererData.lightRenderTextureScale, 0.01f, 1.0f);
|
||||
var width = (int)(renderingData.cameraData.cameraTargetDescriptor.width * renderTextureScale);
|
||||
var height = (int)(renderingData.cameraData.cameraTargetDescriptor.height * renderTextureScale);
|
||||
|
||||
var descriptor = new RenderTextureDescriptor(width, height);
|
||||
descriptor.graphicsFormat = GetRenderTextureFormat();
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.msaaSamples = 1;
|
||||
descriptor.dimension = TextureDimension.Tex2D;
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static void CreateCameraSortingLayerRenderTexture(this IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmd, Downsampling downsamplingMethod)
|
||||
{
|
||||
var renderTextureScale = 1.0f;
|
||||
if (downsamplingMethod == Downsampling._2xBilinear)
|
||||
renderTextureScale = 0.5f;
|
||||
else if (downsamplingMethod == Downsampling._4xBox || downsamplingMethod == Downsampling._4xBilinear)
|
||||
renderTextureScale = 0.25f;
|
||||
|
||||
var width = (int)(renderingData.cameraData.cameraTargetDescriptor.width * renderTextureScale);
|
||||
var height = (int)(renderingData.cameraData.cameraTargetDescriptor.height * renderTextureScale);
|
||||
|
||||
var descriptor = new RenderTextureDescriptor(width, height);
|
||||
descriptor.graphicsFormat = renderingData.cameraData.cameraTargetDescriptor.graphicsFormat;
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
descriptor.depthBufferBits = 0;
|
||||
descriptor.msaaSamples = 1;
|
||||
descriptor.dimension = TextureDimension.Tex2D;
|
||||
|
||||
cmd.GetTemporaryRT(pass.rendererData.cameraSortingLayerRenderTarget.id, descriptor, FilterMode.Bilinear);
|
||||
}
|
||||
|
||||
public static void EnableBlendStyle(CommandBuffer cmd, int blendStyleIndex, bool enabled)
|
||||
{
|
||||
var keyword = k_UseBlendStyleKeywords[blendStyleIndex];
|
||||
|
||||
if (enabled)
|
||||
cmd.EnableShaderKeyword(keyword);
|
||||
else
|
||||
cmd.DisableShaderKeyword(keyword);
|
||||
}
|
||||
|
||||
public static void ReleaseRenderTextures(this IRenderPass2D pass, CommandBuffer cmd)
|
||||
{
|
||||
pass.rendererData.isNormalsRenderTargetValid = false;
|
||||
pass.rendererData.normalsRenderTargetScale = 0.0f;
|
||||
cmd.ReleaseTemporaryRT(pass.rendererData.normalsRenderTarget.id);
|
||||
cmd.ReleaseTemporaryRT(pass.rendererData.shadowsRenderTarget.id);
|
||||
cmd.ReleaseTemporaryRT(pass.rendererData.cameraSortingLayerRenderTarget.id);
|
||||
}
|
||||
|
||||
public static void DrawPointLight(CommandBuffer cmd, Light2D light, Mesh lightMesh, Material material)
|
||||
{
|
||||
var scale = new Vector3(light.pointLightOuterRadius, light.pointLightOuterRadius, light.pointLightOuterRadius);
|
||||
var matrix = Matrix4x4.TRS(light.transform.position, light.transform.rotation, scale);
|
||||
cmd.DrawMesh(lightMesh, matrix, material);
|
||||
}
|
||||
|
||||
private static void RenderLightSet(IRenderPass2D pass, RenderingData renderingData, int blendStyleIndex, CommandBuffer cmd, int layerToRender, RenderTargetIdentifier renderTexture, List<Light2D> lights)
|
||||
{
|
||||
var maxShadowTextureCount = ShadowRendering.maxTextureCount;
|
||||
var requiresRTInit = true;
|
||||
|
||||
// This case should never happen, but if it does it may cause an infinite loop later.
|
||||
if (maxShadowTextureCount < 1)
|
||||
{
|
||||
Debug.LogError("maxShadowTextureCount cannot be less than 1");
|
||||
return;
|
||||
}
|
||||
|
||||
// Break up light rendering into batches for the purpose of shadow casting
|
||||
var lightIndex = 0;
|
||||
while (lightIndex < lights.Count)
|
||||
{
|
||||
var remainingLights = (uint)lights.Count - lightIndex;
|
||||
var batchedLights = 0;
|
||||
|
||||
// Add lights to our batch until the number of shadow textures reach the maxShadowTextureCount
|
||||
var shadowLightCount = 0;
|
||||
while (batchedLights < remainingLights && shadowLightCount < maxShadowTextureCount)
|
||||
{
|
||||
var light = lights[lightIndex + batchedLights];
|
||||
if (light.shadowsEnabled && light.shadowIntensity > 0)
|
||||
{
|
||||
ShadowRendering.CreateShadowRenderTexture(pass, renderingData, cmd, shadowLightCount);
|
||||
ShadowRendering.PrerenderShadows(pass, renderingData, cmd, layerToRender, light, shadowLightCount, light.shadowIntensity);
|
||||
shadowLightCount++;
|
||||
}
|
||||
batchedLights++;
|
||||
}
|
||||
|
||||
// Set the current RT to the light RT
|
||||
if (shadowLightCount > 0 || requiresRTInit)
|
||||
{
|
||||
cmd.SetRenderTarget(renderTexture, RenderBufferLoadAction.Load, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare);
|
||||
requiresRTInit = false;
|
||||
}
|
||||
|
||||
// Render all the lights.
|
||||
shadowLightCount = 0;
|
||||
for (var lightIndexOffset = 0; lightIndexOffset < batchedLights; lightIndexOffset++)
|
||||
{
|
||||
var light = lights[(int)(lightIndex + lightIndexOffset)];
|
||||
|
||||
if (light != null &&
|
||||
light.lightType != Light2D.LightType.Global &&
|
||||
light.blendStyleIndex == blendStyleIndex &&
|
||||
light.IsLitLayer(layerToRender))
|
||||
{
|
||||
// Render light
|
||||
var lightMaterial = pass.rendererData.GetLightMaterial(light, false);
|
||||
if (lightMaterial == null)
|
||||
continue;
|
||||
|
||||
var lightMesh = light.lightMesh;
|
||||
if (lightMesh == null)
|
||||
continue;
|
||||
|
||||
// Set the shadow texture to read from
|
||||
if (light.shadowsEnabled && light.shadowIntensity > 0)
|
||||
ShadowRendering.SetGlobalShadowTexture(cmd, light, shadowLightCount++);
|
||||
else
|
||||
ShadowRendering.DisableGlobalShadowTexture(cmd);
|
||||
|
||||
|
||||
if (light.lightType == Light2D.LightType.Sprite && light.lightCookieSprite != null && light.lightCookieSprite.texture != null)
|
||||
cmd.SetGlobalTexture(k_CookieTexID, light.lightCookieSprite.texture);
|
||||
|
||||
SetGeneralLightShaderGlobals(pass, cmd, light);
|
||||
|
||||
if (light.normalMapQuality != Light2D.NormalMapQuality.Disabled || light.lightType == Light2D.LightType.Point)
|
||||
SetPointLightShaderGlobals(pass, cmd, light);
|
||||
|
||||
// Light code could be combined...
|
||||
if (light.lightType == (Light2D.LightType)Light2D.DeprecatedLightType.Parametric || light.lightType == Light2D.LightType.Freeform || light.lightType == Light2D.LightType.Sprite)
|
||||
{
|
||||
cmd.DrawMesh(lightMesh, light.transform.localToWorldMatrix, lightMaterial);
|
||||
}
|
||||
else if (light.lightType == Light2D.LightType.Point)
|
||||
{
|
||||
DrawPointLight(cmd, light, lightMesh, lightMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Release all of the temporary shadow textures
|
||||
for (var releaseIndex = shadowLightCount - 1; releaseIndex >= 0; releaseIndex--)
|
||||
ShadowRendering.ReleaseShadowRenderTexture(cmd, releaseIndex);
|
||||
|
||||
lightIndex += batchedLights;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderLightVolumes(this IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmd, int layerToRender, int endLayerValue, RenderTargetIdentifier renderTexture, RenderTargetIdentifier depthTexture, List<Light2D> lights)
|
||||
{
|
||||
var maxShadowTextureCount = ShadowRendering.maxTextureCount;
|
||||
var requiresRTInit = true;
|
||||
|
||||
// This case should never happen, but if it does it may cause an infinite loop later.
|
||||
if (maxShadowTextureCount < 1)
|
||||
{
|
||||
Debug.LogError("maxShadowTextureCount cannot be less than 1");
|
||||
return;
|
||||
}
|
||||
|
||||
// Break up light rendering into batches for the purpose of shadow casting
|
||||
var lightIndex = 0;
|
||||
while (lightIndex < lights.Count)
|
||||
{
|
||||
var remainingLights = (uint)lights.Count - lightIndex;
|
||||
var batchedLights = 0;
|
||||
|
||||
// Add lights to our batch until the number of shadow textures reach the maxShadowTextureCount
|
||||
var shadowLightCount = 0;
|
||||
while (batchedLights < remainingLights && shadowLightCount < maxShadowTextureCount)
|
||||
{
|
||||
var light = lights[lightIndex + batchedLights];
|
||||
if (light.volumetricShadowsEnabled && light.shadowVolumeIntensity > 0)
|
||||
{
|
||||
ShadowRendering.CreateShadowRenderTexture(pass, renderingData, cmd, shadowLightCount);
|
||||
ShadowRendering.PrerenderShadows(pass, renderingData, cmd, layerToRender, light, shadowLightCount, light.shadowVolumeIntensity);
|
||||
shadowLightCount++;
|
||||
}
|
||||
batchedLights++;
|
||||
}
|
||||
|
||||
// Set the current RT to the light RT
|
||||
if (shadowLightCount > 0 || requiresRTInit)
|
||||
{
|
||||
cmd.SetRenderTarget(renderTexture, depthTexture);
|
||||
requiresRTInit = false;
|
||||
}
|
||||
|
||||
// Render all the lights.
|
||||
shadowLightCount = 0;
|
||||
for (var lightIndexOffset = 0; lightIndexOffset < batchedLights; lightIndexOffset++)
|
||||
{
|
||||
var light = lights[(int)(lightIndex + lightIndexOffset)];
|
||||
|
||||
if (light.lightType == Light2D.LightType.Global)
|
||||
continue;
|
||||
|
||||
if (light.volumeIntensity <= 0.0f || !light.volumeIntensityEnabled)
|
||||
continue;
|
||||
|
||||
var topMostLayerValue = light.GetTopMostLitLayer();
|
||||
if (endLayerValue == topMostLayerValue) // this implies the layer is correct
|
||||
{
|
||||
var lightVolumeMaterial = pass.rendererData.GetLightMaterial(light, true);
|
||||
var lightMesh = light.lightMesh;
|
||||
|
||||
// Set the shadow texture to read from
|
||||
if (light.volumetricShadowsEnabled && light.shadowVolumeIntensity > 0)
|
||||
ShadowRendering.SetGlobalShadowTexture(cmd, light, shadowLightCount++);
|
||||
else
|
||||
ShadowRendering.DisableGlobalShadowTexture(cmd);
|
||||
|
||||
if (light.lightType == Light2D.LightType.Sprite && light.lightCookieSprite != null && light.lightCookieSprite.texture != null)
|
||||
cmd.SetGlobalTexture(k_CookieTexID, light.lightCookieSprite.texture);
|
||||
|
||||
SetGeneralLightShaderGlobals(pass, cmd, light);
|
||||
|
||||
// Is this needed
|
||||
if (light.normalMapQuality != Light2D.NormalMapQuality.Disabled || light.lightType == Light2D.LightType.Point)
|
||||
SetPointLightShaderGlobals(pass, cmd, light);
|
||||
|
||||
// Could be combined...
|
||||
if (light.lightType == Light2D.LightType.Parametric || light.lightType == Light2D.LightType.Freeform || light.lightType == Light2D.LightType.Sprite)
|
||||
{
|
||||
cmd.DrawMesh(lightMesh, light.transform.localToWorldMatrix, lightVolumeMaterial);
|
||||
}
|
||||
else if (light.lightType == Light2D.LightType.Point)
|
||||
{
|
||||
DrawPointLight(cmd, light, lightMesh, lightVolumeMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Release all of the temporary shadow textures
|
||||
for (var releaseIndex = shadowLightCount - 1; releaseIndex >= 0; releaseIndex--)
|
||||
ShadowRendering.ReleaseShadowRenderTexture(cmd, releaseIndex);
|
||||
|
||||
lightIndex += batchedLights;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetShapeLightShaderGlobals(this IRenderPass2D pass, CommandBuffer cmd)
|
||||
{
|
||||
for (var i = 0; i < pass.rendererData.lightBlendStyles.Length; i++)
|
||||
{
|
||||
var blendStyle = pass.rendererData.lightBlendStyles[i];
|
||||
if (i >= k_BlendFactorsPropIDs.Length)
|
||||
break;
|
||||
|
||||
cmd.SetGlobalVector(k_BlendFactorsPropIDs[i], blendStyle.blendFactors);
|
||||
cmd.SetGlobalVector(k_MaskFilterPropIDs[i], blendStyle.maskTextureChannelFilter.mask);
|
||||
cmd.SetGlobalVector(k_InvertedFilterPropIDs[i], blendStyle.maskTextureChannelFilter.inverted);
|
||||
}
|
||||
|
||||
cmd.SetGlobalTexture(k_FalloffLookupID, pass.rendererData.fallOffLookup);
|
||||
}
|
||||
|
||||
private static float GetNormalizedInnerRadius(Light2D light)
|
||||
{
|
||||
return light.pointLightInnerRadius / light.pointLightOuterRadius;
|
||||
}
|
||||
|
||||
private static float GetNormalizedAngle(float angle)
|
||||
{
|
||||
return (angle / 360.0f);
|
||||
}
|
||||
|
||||
private static void GetScaledLightInvMatrix(Light2D light, out Matrix4x4 retMatrix)
|
||||
{
|
||||
var outerRadius = light.pointLightOuterRadius;
|
||||
var lightScale = Vector3.one;
|
||||
var outerRadiusScale = new Vector3(lightScale.x * outerRadius, lightScale.y * outerRadius, lightScale.z * outerRadius);
|
||||
|
||||
var transform = light.transform;
|
||||
|
||||
var scaledLightMat = Matrix4x4.TRS(transform.position, transform.rotation, outerRadiusScale);
|
||||
retMatrix = Matrix4x4.Inverse(scaledLightMat);
|
||||
}
|
||||
|
||||
private static void SetGeneralLightShaderGlobals(IRenderPass2D pass, CommandBuffer cmd, Light2D light)
|
||||
{
|
||||
float intensity = light.intensity * light.color.a;
|
||||
Color color = intensity * light.color;
|
||||
color.a = 1.0f;
|
||||
|
||||
float volumeIntensity = light.volumeIntensity;
|
||||
|
||||
cmd.SetGlobalFloat(k_FalloffIntensityID, light.falloffIntensity);
|
||||
cmd.SetGlobalFloat(k_FalloffDistanceID, light.shapeLightFalloffSize);
|
||||
cmd.SetGlobalColor(k_LightColorID, color);
|
||||
cmd.SetGlobalFloat(k_VolumeOpacityID, volumeIntensity);
|
||||
}
|
||||
|
||||
private static void SetPointLightShaderGlobals(IRenderPass2D pass, CommandBuffer cmd, Light2D light)
|
||||
{
|
||||
// This is used for the lookup texture
|
||||
GetScaledLightInvMatrix(light, out var lightInverseMatrix);
|
||||
|
||||
var innerRadius = GetNormalizedInnerRadius(light);
|
||||
var innerAngle = GetNormalizedAngle(light.pointLightInnerAngle);
|
||||
var outerAngle = GetNormalizedAngle(light.pointLightOuterAngle);
|
||||
var innerRadiusMult = 1 / (1 - innerRadius);
|
||||
|
||||
cmd.SetGlobalVector(k_LightPositionID, light.transform.position);
|
||||
cmd.SetGlobalMatrix(k_LightInvMatrixID, lightInverseMatrix);
|
||||
cmd.SetGlobalFloat(k_InnerRadiusMultID, innerRadiusMult);
|
||||
cmd.SetGlobalFloat(k_OuterAngleID, outerAngle);
|
||||
cmd.SetGlobalFloat(k_InnerAngleMultID, 1 / (outerAngle - innerAngle));
|
||||
cmd.SetGlobalTexture(k_LightLookupID, Light2DLookupTexture.GetLightLookupTexture());
|
||||
cmd.SetGlobalTexture(k_FalloffLookupID, pass.rendererData.fallOffLookup);
|
||||
cmd.SetGlobalFloat(k_FalloffIntensityID, light.falloffIntensity);
|
||||
cmd.SetGlobalFloat(k_IsFullSpotlightID, innerAngle == 1 ? 1.0f : 0.0f);
|
||||
|
||||
cmd.SetGlobalFloat(k_LightZDistanceID, light.normalMapDistance);
|
||||
|
||||
if (light.lightCookieSprite != null && light.lightCookieSprite.texture != null)
|
||||
cmd.SetGlobalTexture(k_PointLightCookieTexID, light.lightCookieSprite.texture);
|
||||
}
|
||||
|
||||
public static void ClearDirtyLighting(this IRenderPass2D pass, CommandBuffer cmd, uint blendStylesUsed)
|
||||
{
|
||||
for (var i = 0; i < pass.rendererData.lightBlendStyles.Length; ++i)
|
||||
{
|
||||
if ((blendStylesUsed & (uint)(1 << i)) == 0)
|
||||
continue;
|
||||
|
||||
if (!pass.rendererData.lightBlendStyles[i].isDirty)
|
||||
continue;
|
||||
|
||||
cmd.SetRenderTarget(pass.rendererData.lightBlendStyles[i].renderTargetHandle.Identifier());
|
||||
cmd.ClearRenderTarget(false, true, Color.black);
|
||||
pass.rendererData.lightBlendStyles[i].isDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderNormals(this IRenderPass2D pass, ScriptableRenderContext context, RenderingData renderingData, DrawingSettings drawSettings, FilteringSettings filterSettings, RenderTargetIdentifier depthTarget, CommandBuffer cmd, LightStats lightStats)
|
||||
{
|
||||
using (new ProfilingScope(cmd, m_ProfilingSampler))
|
||||
{
|
||||
// figure out the scale
|
||||
var normalRTScale = 0.0f;
|
||||
|
||||
if (depthTarget != BuiltinRenderTextureType.None)
|
||||
normalRTScale = 1.0f;
|
||||
else
|
||||
normalRTScale = Mathf.Clamp(pass.rendererData.lightRenderTextureScale, 0.01f, 1.0f);
|
||||
|
||||
pass.CreateNormalMapRenderTexture(renderingData, cmd, normalRTScale);
|
||||
|
||||
if (depthTarget != BuiltinRenderTextureType.None)
|
||||
{
|
||||
cmd.SetRenderTarget(
|
||||
pass.rendererData.normalsRenderTarget.Identifier(),
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.Store,
|
||||
depthTarget,
|
||||
RenderBufferLoadAction.Load,
|
||||
RenderBufferStoreAction.DontCare);
|
||||
}
|
||||
else
|
||||
cmd.SetRenderTarget(pass.rendererData.normalsRenderTarget.Identifier(), RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
|
||||
|
||||
cmd.ClearRenderTarget(true, true, k_NormalClearColor);
|
||||
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
cmd.Clear();
|
||||
|
||||
drawSettings.SetShaderPassName(0, k_NormalsRenderingPassName);
|
||||
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderLights(this IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmd, int layerToRender, ref LayerBatch layerBatch, ref RenderTextureDescriptor rtDesc)
|
||||
{
|
||||
var blendStyles = pass.rendererData.lightBlendStyles;
|
||||
|
||||
for (var i = 0; i < blendStyles.Length; ++i)
|
||||
{
|
||||
if ((layerBatch.lightStats.blendStylesUsed & (uint)(1 << i)) == 0)
|
||||
continue;
|
||||
|
||||
var sampleName = blendStyles[i].name;
|
||||
cmd.BeginSample(sampleName);
|
||||
|
||||
if (!Light2DManager.GetGlobalColor(layerToRender, i, out var clearColor))
|
||||
clearColor = Color.black;
|
||||
|
||||
var anyLights = (layerBatch.lightStats.blendStylesWithLights & (uint)(1 << i)) != 0;
|
||||
|
||||
var desc = rtDesc;
|
||||
if (!anyLights) // No lights -- create tiny texture
|
||||
desc.width = desc.height = 4;
|
||||
var identifier = layerBatch.GetRTId(cmd, desc, i);
|
||||
|
||||
cmd.SetRenderTarget(identifier,
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.Store,
|
||||
RenderBufferLoadAction.DontCare,
|
||||
RenderBufferStoreAction.DontCare);
|
||||
cmd.ClearRenderTarget(false, true, clearColor);
|
||||
|
||||
if (anyLights)
|
||||
{
|
||||
RenderLightSet(
|
||||
pass, renderingData,
|
||||
i,
|
||||
cmd,
|
||||
layerToRender,
|
||||
identifier,
|
||||
pass.rendererData.lightCullResult.visibleLights
|
||||
);
|
||||
}
|
||||
|
||||
cmd.EndSample(sampleName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetBlendModes(Material material, BlendMode src, BlendMode dst)
|
||||
{
|
||||
material.SetFloat(k_SrcBlendID, (float)src);
|
||||
material.SetFloat(k_DstBlendID, (float)dst);
|
||||
}
|
||||
|
||||
private static uint GetLightMaterialIndex(Light2D light, bool isVolume)
|
||||
{
|
||||
var isPoint = light.isPointLight;
|
||||
var bitIndex = 0;
|
||||
var volumeBit = isVolume ? 1u << bitIndex : 0u;
|
||||
bitIndex++;
|
||||
var shapeBit = !isPoint ? 1u << bitIndex : 0u;
|
||||
bitIndex++;
|
||||
var additiveBit = light.overlapOperation == Light2D.OverlapOperation.AlphaBlend ? 0u : 1u << bitIndex;
|
||||
bitIndex++;
|
||||
var spriteBit = light.lightType == Light2D.LightType.Sprite ? 1u << bitIndex : 0u;
|
||||
bitIndex++;
|
||||
var pointCookieBit = (isPoint && light.lightCookieSprite != null && light.lightCookieSprite.texture != null) ? 1u << bitIndex : 0u;
|
||||
bitIndex++;
|
||||
var pointFastQualityBit = (isPoint && light.normalMapQuality == Light2D.NormalMapQuality.Fast) ? 1u << bitIndex : 0u;
|
||||
bitIndex++;
|
||||
var useNormalMap = light.normalMapQuality != Light2D.NormalMapQuality.Disabled ? 1u << bitIndex : 0u;
|
||||
|
||||
return pointFastQualityBit | pointCookieBit | spriteBit | additiveBit | shapeBit | volumeBit | useNormalMap;
|
||||
}
|
||||
|
||||
private static Material CreateLightMaterial(Renderer2DData rendererData, Light2D light, bool isVolume)
|
||||
{
|
||||
var isPoint = light.isPointLight;
|
||||
Material material;
|
||||
|
||||
if (isVolume)
|
||||
material = CoreUtils.CreateEngineMaterial(isPoint ? rendererData.pointLightVolumeShader : rendererData.shapeLightVolumeShader);
|
||||
else
|
||||
{
|
||||
material = CoreUtils.CreateEngineMaterial(isPoint ? rendererData.pointLightShader : rendererData.shapeLightShader);
|
||||
|
||||
if (light.overlapOperation == Light2D.OverlapOperation.Additive)
|
||||
{
|
||||
SetBlendModes(material, BlendMode.One, BlendMode.One);
|
||||
material.EnableKeyword(k_UseAdditiveBlendingKeyword);
|
||||
}
|
||||
else
|
||||
SetBlendModes(material, BlendMode.SrcAlpha, BlendMode.OneMinusSrcAlpha);
|
||||
}
|
||||
|
||||
if (light.lightType == Light2D.LightType.Sprite)
|
||||
material.EnableKeyword(k_SpriteLightKeyword);
|
||||
|
||||
if (isPoint && light.lightCookieSprite != null && light.lightCookieSprite.texture != null)
|
||||
material.EnableKeyword(k_UsePointLightCookiesKeyword);
|
||||
|
||||
if (isPoint && light.normalMapQuality == Light2D.NormalMapQuality.Fast)
|
||||
material.EnableKeyword(k_LightQualityFastKeyword);
|
||||
|
||||
if (light.normalMapQuality != Light2D.NormalMapQuality.Disabled)
|
||||
material.EnableKeyword(k_UseNormalMap);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
private static Material GetLightMaterial(this Renderer2DData rendererData, Light2D light, bool isVolume)
|
||||
{
|
||||
var materialIndex = GetLightMaterialIndex(light, isVolume);
|
||||
|
||||
if (!rendererData.lightMaterials.TryGetValue(materialIndex, out var material))
|
||||
{
|
||||
material = CreateLightMaterial(rendererData, light, isVolume);
|
||||
rendererData.lightMaterials[materialIndex] = material;
|
||||
}
|
||||
|
||||
return material;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,311 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
/// <summary>
|
||||
/// The Pixel Perfect Camera component ensures your pixel art remains crisp and clear at different resolutions, and stable in motion.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Rendering/2D/Pixel Perfect Camera (Experimental)")]
|
||||
[RequireComponent(typeof(Camera))]
|
||||
[MovedFrom("UnityEngine.Experimental.Rendering.LWRP")]
|
||||
[HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2d-pixelperfect.html%23properties")]
|
||||
public class PixelPerfectCamera : MonoBehaviour, IPixelPerfectCamera
|
||||
{
|
||||
/// <summary>
|
||||
/// Match this value to to the Pixels Per Unit values of all Sprites within the Scene.
|
||||
/// </summary>
|
||||
public int assetsPPU { get { return m_AssetsPPU; } set { m_AssetsPPU = value > 0 ? value : 1; } }
|
||||
|
||||
/// <summary>
|
||||
/// The original horizontal resolution your Assets are designed for.
|
||||
/// </summary>
|
||||
public int refResolutionX { get { return m_RefResolutionX; } set { m_RefResolutionX = value > 0 ? value : 1; } }
|
||||
|
||||
/// <summary>
|
||||
/// Original vertical resolution your Assets are designed for.
|
||||
/// </summary>
|
||||
public int refResolutionY { get { return m_RefResolutionY; } set { m_RefResolutionY = value > 0 ? value : 1; } }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to have the Scene rendered to a temporary texture set as close as possible to the Reference Resolution,
|
||||
/// while maintaining the full screen aspect ratio. This temporary texture is then upscaled to fit the full screen.
|
||||
/// </summary>
|
||||
public bool upscaleRT { get { return m_UpscaleRT; } set { m_UpscaleRT = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to prevent subpixel movement and make Sprites appear to move in pixel-by-pixel increments.
|
||||
/// Only applicable when upscaleRT is false.
|
||||
/// </summary>
|
||||
public bool pixelSnapping { get { return m_PixelSnapping; } set { m_PixelSnapping = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to crop the viewport with black bars to match refResolutionX in the horizontal direction.
|
||||
/// </summary>
|
||||
public bool cropFrameX { get { return m_CropFrameX; } set { m_CropFrameX = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to crop the viewport with black bars to match refResolutionY in the vertical direction.
|
||||
/// </summary>
|
||||
public bool cropFrameY { get { return m_CropFrameY; } set { m_CropFrameY = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Set to true to expand the viewport to fit the screen resolution while maintaining the viewport's aspect ratio.
|
||||
/// Only applicable when both cropFrameX and cropFrameY are true.
|
||||
/// </summary>
|
||||
public bool stretchFill { get { return m_StretchFill; } set { m_StretchFill = value; } }
|
||||
|
||||
/// <summary>
|
||||
/// Ratio of the rendered Sprites compared to their original size (readonly).
|
||||
/// </summary>
|
||||
public int pixelRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CinemachineCompatibilityMode)
|
||||
{
|
||||
if (m_UpscaleRT)
|
||||
return m_Internal.zoom * m_Internal.cinemachineVCamZoom;
|
||||
else
|
||||
return m_Internal.cinemachineVCamZoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_Internal.zoom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Round a arbitrary position to an integer pixel position. Works in world space.
|
||||
/// </summary>
|
||||
/// <param name="position"> The position you want to round.</param>
|
||||
/// <returns>
|
||||
/// The rounded pixel position.
|
||||
/// Depending on the values of upscaleRT and pixelSnapping, it could be a screen pixel position or an art pixel position.
|
||||
/// </returns>
|
||||
public Vector3 RoundToPixel(Vector3 position)
|
||||
{
|
||||
float unitsPerPixel = m_Internal.unitsPerPixel;
|
||||
if (unitsPerPixel == 0.0f)
|
||||
return position;
|
||||
|
||||
Vector3 result;
|
||||
result.x = Mathf.Round(position.x / unitsPerPixel) * unitsPerPixel;
|
||||
result.y = Mathf.Round(position.y / unitsPerPixel) * unitsPerPixel;
|
||||
result.z = Mathf.Round(position.z / unitsPerPixel) * unitsPerPixel;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find a pixel-perfect orthographic size as close to targetOrthoSize as possible. Used by Cinemachine to solve compatibility issues with Pixel Perfect Camera.
|
||||
/// </summary>
|
||||
/// <param name="targetOrthoSize">Orthographic size from the live Cinemachine Virtual Camera.</param>
|
||||
/// <returns>The corrected orthographic size.</returns>
|
||||
public float CorrectCinemachineOrthoSize(float targetOrthoSize)
|
||||
{
|
||||
m_CinemachineCompatibilityMode = true;
|
||||
|
||||
if (m_Internal == null)
|
||||
return targetOrthoSize;
|
||||
else
|
||||
return m_Internal.CorrectCinemachineOrthoSize(targetOrthoSize);
|
||||
}
|
||||
|
||||
[SerializeField] int m_AssetsPPU = 100;
|
||||
[SerializeField] int m_RefResolutionX = 320;
|
||||
[SerializeField] int m_RefResolutionY = 180;
|
||||
[SerializeField] bool m_UpscaleRT;
|
||||
[SerializeField] bool m_PixelSnapping;
|
||||
[SerializeField] bool m_CropFrameX;
|
||||
[SerializeField] bool m_CropFrameY;
|
||||
[SerializeField] bool m_StretchFill;
|
||||
|
||||
Camera m_Camera;
|
||||
PixelPerfectCameraInternal m_Internal;
|
||||
bool m_CinemachineCompatibilityMode;
|
||||
|
||||
internal bool isRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return (Application.isPlaying || runInEditMode) && enabled;
|
||||
#else
|
||||
return enabled;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
internal FilterMode finalBlitFilterMode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isRunning)
|
||||
return FilterMode.Bilinear;
|
||||
else
|
||||
return m_Internal.useStretchFill ? FilterMode.Bilinear : FilterMode.Point;
|
||||
}
|
||||
}
|
||||
|
||||
internal Vector2Int offscreenRTSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isRunning)
|
||||
return Vector2Int.zero;
|
||||
else
|
||||
return new Vector2Int(m_Internal.offscreenRTWidth, m_Internal.offscreenRTHeight);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2Int cameraRTSize
|
||||
{
|
||||
get
|
||||
{
|
||||
var targetTexture = m_Camera.targetTexture;
|
||||
return targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
|
||||
}
|
||||
}
|
||||
|
||||
// Snap camera position to pixels using Camera.worldToCameraMatrix.
|
||||
void PixelSnap()
|
||||
{
|
||||
Vector3 cameraPosition = m_Camera.transform.position;
|
||||
Vector3 roundedCameraPosition = RoundToPixel(cameraPosition);
|
||||
Vector3 offset = roundedCameraPosition - cameraPosition;
|
||||
offset.z = -offset.z;
|
||||
Matrix4x4 offsetMatrix = Matrix4x4.TRS(-offset, Quaternion.identity, new Vector3(1.0f, 1.0f, -1.0f));
|
||||
|
||||
m_Camera.worldToCameraMatrix = offsetMatrix * m_Camera.transform.worldToLocalMatrix;
|
||||
}
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Camera = GetComponent<Camera>();
|
||||
m_Internal = new PixelPerfectCameraInternal(this);
|
||||
|
||||
m_Internal.originalOrthoSize = m_Camera.orthographicSize;
|
||||
|
||||
// Case 1249076: Initialize internals immediately after the scene is loaded,
|
||||
// as the Cinemachine extension may need them before OnBeginContextRendering is called.
|
||||
var rtSize = cameraRTSize;
|
||||
m_Internal.CalculateCameraProperties(rtSize.x, rtSize.y);
|
||||
}
|
||||
|
||||
void OnBeginContextRendering(ScriptableRenderContext context, List<Camera> cameras)
|
||||
{
|
||||
var rtSize = cameraRTSize;
|
||||
m_Internal.CalculateCameraProperties(rtSize.x, rtSize.y);
|
||||
|
||||
PixelSnap();
|
||||
|
||||
if (m_Internal.useOffscreenRT)
|
||||
m_Camera.pixelRect = m_Internal.CalculateFinalBlitPixelRect(rtSize.x, rtSize.y);
|
||||
else
|
||||
m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
|
||||
// In Cinemachine compatibility mode the control over orthographic size should
|
||||
// be given to the virtual cameras, whose orthographic sizes will be corrected to
|
||||
// be pixel-perfect. This way when there's blending between virtual cameras, we
|
||||
// can have temporary not-pixel-perfect but smooth transitions.
|
||||
if (!m_CinemachineCompatibilityMode)
|
||||
{
|
||||
m_Camera.orthographicSize = m_Internal.orthoSize;
|
||||
}
|
||||
}
|
||||
|
||||
void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (camera == m_Camera)
|
||||
UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = m_Internal.unitsPerPixel;
|
||||
}
|
||||
|
||||
void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
|
||||
{
|
||||
if (camera == m_Camera)
|
||||
UnityEngine.U2D.PixelPerfectRendering.pixelSnapSpacing = 0.0f;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_CinemachineCompatibilityMode = false;
|
||||
|
||||
RenderPipelineManager.beginContextRendering += OnBeginContextRendering;
|
||||
RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
|
||||
RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying)
|
||||
UnityEditor.EditorApplication.playModeStateChanged += OnPlayModeChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
internal void OnDisable()
|
||||
{
|
||||
RenderPipelineManager.beginContextRendering -= OnBeginContextRendering;
|
||||
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
|
||||
RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
|
||||
|
||||
m_Camera.rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
m_Camera.orthographicSize = m_Internal.originalOrthoSize;
|
||||
m_Camera.ResetWorldToCameraMatrix();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying)
|
||||
UnityEditor.EditorApplication.playModeStateChanged -= OnPlayModeChanged;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
// Show on-screen warning about invalid render resolutions.
|
||||
void OnGUI()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!UnityEditor.EditorApplication.isPlaying && !runInEditMode)
|
||||
return;
|
||||
#endif
|
||||
|
||||
Color oldColor = GUI.color;
|
||||
GUI.color = Color.red;
|
||||
|
||||
Vector2Int renderResolution = Vector2Int.zero;
|
||||
renderResolution.x = m_Internal.useOffscreenRT ? m_Internal.offscreenRTWidth : m_Camera.pixelWidth;
|
||||
renderResolution.y = m_Internal.useOffscreenRT ? m_Internal.offscreenRTHeight : m_Camera.pixelHeight;
|
||||
|
||||
if (renderResolution.x % 2 != 0 || renderResolution.y % 2 != 0)
|
||||
{
|
||||
string warning = string.Format("Rendering at an odd-numbered resolution ({0} * {1}). Pixel Perfect Camera may not work properly in this situation.", renderResolution.x, renderResolution.y);
|
||||
GUILayout.Box(warning);
|
||||
}
|
||||
|
||||
var targetTexture = m_Camera.targetTexture;
|
||||
Vector2Int rtSize = targetTexture == null ? new Vector2Int(Screen.width, Screen.height) : new Vector2Int(targetTexture.width, targetTexture.height);
|
||||
|
||||
if (rtSize.x < refResolutionX || rtSize.y < refResolutionY)
|
||||
{
|
||||
GUILayout.Box("Target resolution is smaller than the reference resolution. Image may appear stretched or cropped.");
|
||||
}
|
||||
|
||||
GUI.color = oldColor;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnPlayModeChanged(UnityEditor.PlayModeStateChange state)
|
||||
{
|
||||
// Stop running in edit mode when entering play mode.
|
||||
if (state == UnityEditor.PlayModeStateChange.ExitingEditMode)
|
||||
{
|
||||
runInEditMode = false;
|
||||
OnDisable();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal interface IPixelPerfectCamera
|
||||
{
|
||||
int assetsPPU { get; set; }
|
||||
int refResolutionX { get; set; }
|
||||
int refResolutionY { get; set; }
|
||||
bool upscaleRT { get; set; }
|
||||
bool pixelSnapping { get; set; }
|
||||
bool cropFrameX { get; set; }
|
||||
bool cropFrameY { get; set; }
|
||||
bool stretchFill { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class PixelPerfectCameraInternal : ISerializationCallbackReceiver
|
||||
{
|
||||
// Case 1061634:
|
||||
// In order for this class to survive hot reloading, we need to make the fields serializable.
|
||||
// Unity can't serialize an interface object, but does properly serialize UnityEngine.Object.
|
||||
// So we cast the reference to PixelPerfectCamera (which inherits UnityEngine.Object)
|
||||
// before serialization happens, and restore the interface reference after deserialization.
|
||||
[NonSerialized]
|
||||
IPixelPerfectCamera m_Component;
|
||||
PixelPerfectCamera m_SerializableComponent;
|
||||
|
||||
internal float originalOrthoSize;
|
||||
internal bool hasPostProcessLayer;
|
||||
internal bool cropFrameXAndY = false;
|
||||
internal bool cropFrameXOrY = false;
|
||||
internal bool useStretchFill = false;
|
||||
internal int zoom = 1;
|
||||
internal bool useOffscreenRT = false;
|
||||
internal int offscreenRTWidth = 0;
|
||||
internal int offscreenRTHeight = 0;
|
||||
internal Rect pixelRect = Rect.zero;
|
||||
internal float orthoSize = 1.0f;
|
||||
internal float unitsPerPixel = 0.0f;
|
||||
internal int cinemachineVCamZoom = 1;
|
||||
|
||||
internal PixelPerfectCameraInternal(IPixelPerfectCamera component)
|
||||
{
|
||||
m_Component = component;
|
||||
}
|
||||
|
||||
public void OnBeforeSerialize()
|
||||
{
|
||||
m_SerializableComponent = m_Component as PixelPerfectCamera;
|
||||
}
|
||||
|
||||
public void OnAfterDeserialize()
|
||||
{
|
||||
if (m_SerializableComponent != null)
|
||||
m_Component = m_SerializableComponent;
|
||||
}
|
||||
|
||||
internal void CalculateCameraProperties(int screenWidth, int screenHeight)
|
||||
{
|
||||
int assetsPPU = m_Component.assetsPPU;
|
||||
int refResolutionX = m_Component.refResolutionX;
|
||||
int refResolutionY = m_Component.refResolutionY;
|
||||
bool upscaleRT = m_Component.upscaleRT;
|
||||
bool pixelSnapping = m_Component.pixelSnapping;
|
||||
bool cropFrameX = m_Component.cropFrameX;
|
||||
bool cropFrameY = m_Component.cropFrameY;
|
||||
bool stretchFill = m_Component.stretchFill;
|
||||
|
||||
cropFrameXAndY = cropFrameY && cropFrameX;
|
||||
cropFrameXOrY = cropFrameY || cropFrameX;
|
||||
useStretchFill = cropFrameXAndY && stretchFill;
|
||||
|
||||
// zoom level (PPU scale)
|
||||
int verticalZoom = screenHeight / refResolutionY;
|
||||
int horizontalZoom = screenWidth / refResolutionX;
|
||||
zoom = Math.Max(1, Math.Min(verticalZoom, horizontalZoom));
|
||||
|
||||
// off-screen RT
|
||||
useOffscreenRT = false;
|
||||
offscreenRTWidth = 0;
|
||||
offscreenRTHeight = 0;
|
||||
|
||||
if (cropFrameXOrY)
|
||||
{
|
||||
useOffscreenRT = true;
|
||||
|
||||
if (!upscaleRT)
|
||||
{
|
||||
if (cropFrameXAndY)
|
||||
{
|
||||
offscreenRTWidth = zoom * refResolutionX;
|
||||
offscreenRTHeight = zoom * refResolutionY;
|
||||
}
|
||||
else if (cropFrameY)
|
||||
{
|
||||
offscreenRTWidth = screenWidth;
|
||||
offscreenRTHeight = zoom * refResolutionY;
|
||||
}
|
||||
else // crop frame X
|
||||
{
|
||||
offscreenRTWidth = zoom * refResolutionX;
|
||||
offscreenRTHeight = screenHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cropFrameXAndY)
|
||||
{
|
||||
offscreenRTWidth = refResolutionX;
|
||||
offscreenRTHeight = refResolutionY;
|
||||
}
|
||||
else if (cropFrameY)
|
||||
{
|
||||
offscreenRTWidth = screenWidth / zoom / 2 * 2; // Make sure it's an even number by / 2 * 2.
|
||||
offscreenRTHeight = refResolutionY;
|
||||
}
|
||||
else // crop frame X
|
||||
{
|
||||
offscreenRTWidth = refResolutionX;
|
||||
offscreenRTHeight = screenHeight / zoom / 2 * 2; // Make sure it's an even number by / 2 * 2.
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (upscaleRT && zoom > 1)
|
||||
{
|
||||
useOffscreenRT = true;
|
||||
offscreenRTWidth = screenWidth / zoom / 2 * 2; // Make sure it's an even number by / 2 * 2.
|
||||
offscreenRTHeight = screenHeight / zoom / 2 * 2;
|
||||
}
|
||||
|
||||
// viewport
|
||||
if (useOffscreenRT)
|
||||
{
|
||||
// When we ask the render pipeline to create the offscreen RT for us, the size of the RT is determined by VP size.
|
||||
// That's why we set the VP size to be (m_OffscreenRTWidth, m_OffscreenRTHeight) here.
|
||||
pixelRect = new Rect(0.0f, 0.0f, offscreenRTWidth, offscreenRTHeight);
|
||||
}
|
||||
else
|
||||
pixelRect = Rect.zero;
|
||||
|
||||
// orthographic size
|
||||
if (cropFrameY)
|
||||
orthoSize = (refResolutionY * 0.5f) / assetsPPU;
|
||||
else if (cropFrameX)
|
||||
{
|
||||
float aspect = (pixelRect == Rect.zero) ? (float)screenWidth / screenHeight : pixelRect.width / pixelRect.height;
|
||||
orthoSize = ((refResolutionX / aspect) * 0.5f) / assetsPPU;
|
||||
}
|
||||
else if (upscaleRT && zoom > 1)
|
||||
orthoSize = (offscreenRTHeight * 0.5f) / assetsPPU;
|
||||
else
|
||||
{
|
||||
float pixelHeight = (pixelRect == Rect.zero) ? screenHeight : pixelRect.height;
|
||||
orthoSize = (pixelHeight * 0.5f) / (zoom * assetsPPU);
|
||||
}
|
||||
|
||||
// Camera pixel grid spacing
|
||||
if (upscaleRT || (!upscaleRT && pixelSnapping))
|
||||
unitsPerPixel = 1.0f / assetsPPU;
|
||||
else
|
||||
unitsPerPixel = 1.0f / (zoom * assetsPPU);
|
||||
}
|
||||
|
||||
internal Rect CalculateFinalBlitPixelRect(int screenWidth, int screenHeight)
|
||||
{
|
||||
// This VP is used when the internal temp RT is blitted back to screen.
|
||||
Rect pixelRect = new Rect();
|
||||
|
||||
if (useStretchFill)
|
||||
{
|
||||
// stretch (fit either width or height)
|
||||
float screenAspect = (float)screenWidth / screenHeight;
|
||||
float cameraAspect = (float)m_Component.refResolutionX / m_Component.refResolutionY;
|
||||
|
||||
if (screenAspect > cameraAspect)
|
||||
{
|
||||
pixelRect.height = screenHeight;
|
||||
pixelRect.width = screenHeight * cameraAspect;
|
||||
pixelRect.x = (screenWidth - (int)pixelRect.width) / 2;
|
||||
pixelRect.y = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixelRect.width = screenWidth;
|
||||
pixelRect.height = screenWidth / cameraAspect;
|
||||
pixelRect.y = (screenHeight - (int)pixelRect.height) / 2;
|
||||
pixelRect.x = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// center
|
||||
if (m_Component.upscaleRT)
|
||||
{
|
||||
pixelRect.height = zoom * offscreenRTHeight;
|
||||
pixelRect.width = zoom * offscreenRTWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixelRect.height = offscreenRTHeight;
|
||||
pixelRect.width = offscreenRTWidth;
|
||||
}
|
||||
|
||||
pixelRect.x = (screenWidth - (int)pixelRect.width) / 2;
|
||||
pixelRect.y = (screenHeight - (int)pixelRect.height) / 2;
|
||||
}
|
||||
|
||||
return pixelRect;
|
||||
}
|
||||
|
||||
// Find a pixel-perfect orthographic size as close to targetOrthoSize as possible.
|
||||
internal float CorrectCinemachineOrthoSize(float targetOrthoSize)
|
||||
{
|
||||
float correctedOrthoSize;
|
||||
|
||||
if (m_Component.upscaleRT)
|
||||
{
|
||||
cinemachineVCamZoom = Math.Max(1, Mathf.RoundToInt(orthoSize / targetOrthoSize));
|
||||
correctedOrthoSize = orthoSize / cinemachineVCamZoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
cinemachineVCamZoom = Math.Max(1, Mathf.RoundToInt(zoom * orthoSize / targetOrthoSize));
|
||||
correctedOrthoSize = zoom * orthoSize / cinemachineVCamZoom;
|
||||
}
|
||||
|
||||
// In this case the actual zoom level is cinemachineVCamZoom instead of zoom.
|
||||
if (!m_Component.upscaleRT && !m_Component.pixelSnapping)
|
||||
unitsPerPixel = 1.0f / (cinemachineVCamZoom * m_Component.assetsPPU);
|
||||
|
||||
return correctedOrthoSize;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,257 @@
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Rendering.Universal.Internal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal class Renderer2D : ScriptableRenderer
|
||||
{
|
||||
Render2DLightingPass m_Render2DLightingPass;
|
||||
PixelPerfectBackgroundPass m_PixelPerfectBackgroundPass;
|
||||
FinalBlitPass m_FinalBlitPass;
|
||||
Light2DCullResult m_LightCullResult;
|
||||
|
||||
private static readonly ProfilingSampler m_ProfilingSampler = new ProfilingSampler("Create Camera Textures");
|
||||
|
||||
bool m_UseDepthStencilBuffer = true;
|
||||
bool m_CreateColorTexture;
|
||||
bool m_CreateDepthTexture;
|
||||
|
||||
readonly RenderTargetHandle k_ColorTextureHandle;
|
||||
readonly RenderTargetHandle k_DepthTextureHandle;
|
||||
|
||||
Material m_BlitMaterial;
|
||||
Material m_SamplingMaterial;
|
||||
|
||||
Renderer2DData m_Renderer2DData;
|
||||
|
||||
internal bool createColorTexture => m_CreateColorTexture;
|
||||
internal bool createDepthTexture => m_CreateDepthTexture;
|
||||
|
||||
PostProcessPasses m_PostProcessPasses;
|
||||
internal ColorGradingLutPass colorGradingLutPass { get => m_PostProcessPasses.colorGradingLutPass; }
|
||||
internal PostProcessPass postProcessPass { get => m_PostProcessPasses.postProcessPass; }
|
||||
internal PostProcessPass finalPostProcessPass { get => m_PostProcessPasses.finalPostProcessPass; }
|
||||
internal RenderTargetHandle afterPostProcessColorHandle { get => m_PostProcessPasses.afterPostProcessColor; }
|
||||
internal RenderTargetHandle colorGradingLutHandle { get => m_PostProcessPasses.colorGradingLut; }
|
||||
|
||||
public Renderer2D(Renderer2DData data) : base(data)
|
||||
{
|
||||
m_BlitMaterial = CoreUtils.CreateEngineMaterial(data.blitShader);
|
||||
m_SamplingMaterial = CoreUtils.CreateEngineMaterial(data.samplingShader);
|
||||
|
||||
m_Render2DLightingPass = new Render2DLightingPass(data, m_BlitMaterial, m_SamplingMaterial);
|
||||
// we should determine why clearing the camera target is set so late in the events... sounds like it could be earlier
|
||||
m_PixelPerfectBackgroundPass = new PixelPerfectBackgroundPass(RenderPassEvent.AfterRenderingTransparents);
|
||||
m_FinalBlitPass = new FinalBlitPass(RenderPassEvent.AfterRendering + 1, m_BlitMaterial);
|
||||
|
||||
m_PostProcessPasses = new PostProcessPasses(data.postProcessData, m_BlitMaterial);
|
||||
|
||||
m_UseDepthStencilBuffer = data.useDepthStencilBuffer;
|
||||
|
||||
// We probably should declare these names in the base class,
|
||||
// as they must be the same across all ScriptableRenderer types for camera stacking to work.
|
||||
k_ColorTextureHandle.Init("_CameraColorTexture");
|
||||
k_DepthTextureHandle.Init("_CameraDepthAttachment");
|
||||
|
||||
m_Renderer2DData = data;
|
||||
|
||||
supportedRenderingFeatures = new RenderingFeatures()
|
||||
{
|
||||
cameraStacking = true,
|
||||
};
|
||||
|
||||
m_LightCullResult = new Light2DCullResult();
|
||||
m_Renderer2DData.lightCullResult = m_LightCullResult;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
m_PostProcessPasses.Dispose();
|
||||
}
|
||||
|
||||
public Renderer2DData GetRenderer2DData()
|
||||
{
|
||||
return m_Renderer2DData;
|
||||
}
|
||||
|
||||
void CreateRenderTextures(
|
||||
ref CameraData cameraData,
|
||||
bool forceCreateColorTexture,
|
||||
FilterMode colorTextureFilterMode,
|
||||
CommandBuffer cmd,
|
||||
out RenderTargetHandle colorTargetHandle,
|
||||
out RenderTargetHandle depthTargetHandle)
|
||||
{
|
||||
ref var cameraTargetDescriptor = ref cameraData.cameraTargetDescriptor;
|
||||
|
||||
if (cameraData.renderType == CameraRenderType.Base)
|
||||
{
|
||||
m_CreateColorTexture = forceCreateColorTexture
|
||||
|| cameraData.postProcessEnabled
|
||||
|| cameraData.isHdrEnabled
|
||||
|| cameraData.isSceneViewCamera
|
||||
|| !cameraData.isDefaultViewport
|
||||
|| cameraData.requireSrgbConversion
|
||||
|| !m_UseDepthStencilBuffer
|
||||
|| !cameraData.resolveFinalTarget
|
||||
|| m_Renderer2DData.useCameraSortingLayerTexture
|
||||
|| !Mathf.Approximately(cameraData.renderScale, 1.0f);
|
||||
|
||||
m_CreateDepthTexture = !cameraData.resolveFinalTarget && m_UseDepthStencilBuffer;
|
||||
|
||||
colorTargetHandle = m_CreateColorTexture ? k_ColorTextureHandle : RenderTargetHandle.CameraTarget;
|
||||
depthTargetHandle = m_CreateDepthTexture ? k_DepthTextureHandle : colorTargetHandle;
|
||||
|
||||
if (m_CreateColorTexture)
|
||||
{
|
||||
var colorDescriptor = cameraTargetDescriptor;
|
||||
colorDescriptor.depthBufferBits = m_CreateDepthTexture || !m_UseDepthStencilBuffer ? 0 : 32;
|
||||
cmd.GetTemporaryRT(k_ColorTextureHandle.id, colorDescriptor, colorTextureFilterMode);
|
||||
}
|
||||
|
||||
if (m_CreateDepthTexture)
|
||||
{
|
||||
var depthDescriptor = cameraTargetDescriptor;
|
||||
depthDescriptor.colorFormat = RenderTextureFormat.Depth;
|
||||
depthDescriptor.depthBufferBits = 32;
|
||||
depthDescriptor.bindMS = depthDescriptor.msaaSamples > 1 && !SystemInfo.supportsMultisampleAutoResolve && (SystemInfo.supportsMultisampledTextures != 0);
|
||||
cmd.GetTemporaryRT(k_DepthTextureHandle.id, depthDescriptor, FilterMode.Point);
|
||||
}
|
||||
}
|
||||
else // Overlay camera
|
||||
{
|
||||
// These render textures are created by the base camera, but it's the responsibility of the last overlay camera's ScriptableRenderer
|
||||
// to release the textures in its FinishRendering().
|
||||
m_CreateColorTexture = true;
|
||||
m_CreateDepthTexture = true;
|
||||
|
||||
colorTargetHandle = k_ColorTextureHandle;
|
||||
depthTargetHandle = k_DepthTextureHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
|
||||
{
|
||||
ref CameraData cameraData = ref renderingData.cameraData;
|
||||
ref var cameraTargetDescriptor = ref cameraData.cameraTargetDescriptor;
|
||||
bool stackHasPostProcess = renderingData.postProcessingEnabled;
|
||||
bool lastCameraInStack = cameraData.resolveFinalTarget;
|
||||
var colorTextureFilterMode = FilterMode.Bilinear;
|
||||
|
||||
PixelPerfectCamera ppc = null;
|
||||
bool ppcUsesOffscreenRT = false;
|
||||
bool ppcUpscaleRT = false;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// The scene view camera cannot be uninitialized or skybox when using the 2D renderer.
|
||||
if (cameraData.cameraType == CameraType.SceneView)
|
||||
{
|
||||
renderingData.cameraData.camera.clearFlags = CameraClearFlags.SolidColor;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Pixel Perfect Camera doesn't support camera stacking.
|
||||
if (cameraData.renderType == CameraRenderType.Base && lastCameraInStack)
|
||||
{
|
||||
cameraData.camera.TryGetComponent(out ppc);
|
||||
if (ppc != null)
|
||||
{
|
||||
if (ppc.offscreenRTSize != Vector2Int.zero)
|
||||
{
|
||||
ppcUsesOffscreenRT = true;
|
||||
|
||||
// Pixel Perfect Camera may request a different RT size than camera VP size.
|
||||
// In that case we need to modify cameraTargetDescriptor here so that all the passes would use the same size.
|
||||
cameraTargetDescriptor.width = ppc.offscreenRTSize.x;
|
||||
cameraTargetDescriptor.height = ppc.offscreenRTSize.y;
|
||||
}
|
||||
|
||||
colorTextureFilterMode = ppc.finalBlitFilterMode;
|
||||
ppcUpscaleRT = ppc.upscaleRT && ppc.isRunning;
|
||||
}
|
||||
}
|
||||
|
||||
RenderTargetHandle colorTargetHandle;
|
||||
RenderTargetHandle depthTargetHandle;
|
||||
|
||||
CommandBuffer cmd = CommandBufferPool.Get();
|
||||
using (new ProfilingScope(cmd, m_ProfilingSampler))
|
||||
{
|
||||
CreateRenderTextures(ref cameraData, ppcUsesOffscreenRT, colorTextureFilterMode, cmd,
|
||||
out colorTargetHandle, out depthTargetHandle);
|
||||
}
|
||||
context.ExecuteCommandBuffer(cmd);
|
||||
CommandBufferPool.Release(cmd);
|
||||
|
||||
ConfigureCameraTarget(colorTargetHandle.Identifier(), depthTargetHandle.Identifier());
|
||||
|
||||
// We generate color LUT in the base camera only. This allows us to not break render pass execution for overlay cameras.
|
||||
if (stackHasPostProcess && cameraData.renderType == CameraRenderType.Base && m_PostProcessPasses.isCreated)
|
||||
{
|
||||
colorGradingLutPass.Setup(colorGradingLutHandle);
|
||||
EnqueuePass(colorGradingLutPass);
|
||||
}
|
||||
|
||||
var hasValidDepth = m_CreateDepthTexture || !m_CreateColorTexture || m_UseDepthStencilBuffer;
|
||||
m_Render2DLightingPass.Setup(hasValidDepth);
|
||||
m_Render2DLightingPass.ConfigureTarget(colorTargetHandle.Identifier(), depthTargetHandle.Identifier());
|
||||
EnqueuePass(m_Render2DLightingPass);
|
||||
|
||||
// When using Upscale Render Texture on a Pixel Perfect Camera, we want all post-processing effects done with a low-res RT,
|
||||
// and only upscale the low-res RT to fullscreen when blitting it to camera target. Also, final post processing pass is not run in this case,
|
||||
// so FXAA is not supported (you don't want to apply FXAA when everything is intentionally pixelated).
|
||||
bool requireFinalPostProcessPass =
|
||||
lastCameraInStack && !ppcUpscaleRT && stackHasPostProcess && cameraData.antialiasing == AntialiasingMode.FastApproximateAntialiasing;
|
||||
|
||||
if (stackHasPostProcess && m_PostProcessPasses.isCreated)
|
||||
{
|
||||
RenderTargetHandle postProcessDestHandle =
|
||||
lastCameraInStack && !ppcUpscaleRT && !requireFinalPostProcessPass ? RenderTargetHandle.CameraTarget : afterPostProcessColorHandle;
|
||||
|
||||
postProcessPass.Setup(
|
||||
cameraTargetDescriptor,
|
||||
colorTargetHandle,
|
||||
postProcessDestHandle,
|
||||
depthTargetHandle,
|
||||
colorGradingLutHandle,
|
||||
requireFinalPostProcessPass,
|
||||
postProcessDestHandle == RenderTargetHandle.CameraTarget);
|
||||
|
||||
EnqueuePass(postProcessPass);
|
||||
colorTargetHandle = postProcessDestHandle;
|
||||
}
|
||||
|
||||
if (ppc != null && ppc.isRunning && (ppc.cropFrameX || ppc.cropFrameY))
|
||||
EnqueuePass(m_PixelPerfectBackgroundPass);
|
||||
|
||||
if (requireFinalPostProcessPass && m_PostProcessPasses.isCreated)
|
||||
{
|
||||
finalPostProcessPass.SetupFinalPass(colorTargetHandle);
|
||||
EnqueuePass(finalPostProcessPass);
|
||||
}
|
||||
else if (lastCameraInStack && colorTargetHandle != RenderTargetHandle.CameraTarget)
|
||||
{
|
||||
m_FinalBlitPass.Setup(cameraTargetDescriptor, colorTargetHandle);
|
||||
EnqueuePass(m_FinalBlitPass);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetupCullingParameters(ref ScriptableCullingParameters cullingParameters, ref CameraData cameraData)
|
||||
{
|
||||
cullingParameters.cullingOptions = CullingOptions.None;
|
||||
cullingParameters.isOrthographic = cameraData.camera.orthographic;
|
||||
cullingParameters.shadowDistance = 0.0f;
|
||||
m_LightCullResult.SetupCulling(ref cullingParameters, cameraData.camera);
|
||||
}
|
||||
|
||||
public override void FinishRendering(CommandBuffer cmd)
|
||||
{
|
||||
if (m_CreateColorTexture)
|
||||
cmd.ReleaseTemporaryRT(k_ColorTextureHandle.id);
|
||||
|
||||
if (m_CreateDepthTexture)
|
||||
cmd.ReleaseTemporaryRT(k_DepthTextureHandle.id);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using UnityEngine.Scripting.APIUpdating;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.ProjectWindowCallback;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
[Serializable, ReloadGroup, ExcludeFromPreset]
|
||||
[MovedFrom("UnityEngine.Experimental.Rendering.LWRP")]
|
||||
[HelpURL("https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@latest/index.html?subfolder=/manual/2DRendererData_overview.html")]
|
||||
public partial class Renderer2DData : ScriptableRendererData
|
||||
{
|
||||
public enum Renderer2DDefaultMaterialType
|
||||
{
|
||||
Lit,
|
||||
Unlit,
|
||||
Custom
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
TransparencySortMode m_TransparencySortMode = TransparencySortMode.Default;
|
||||
|
||||
[SerializeField]
|
||||
Vector3 m_TransparencySortAxis = Vector3.up;
|
||||
|
||||
[SerializeField]
|
||||
float m_HDREmulationScale = 1;
|
||||
|
||||
[SerializeField, Range(0.01f, 1.0f)]
|
||||
float m_LightRenderTextureScale = 0.5f;
|
||||
|
||||
[SerializeField, FormerlySerializedAs("m_LightOperations")]
|
||||
Light2DBlendStyle[] m_LightBlendStyles = null;
|
||||
|
||||
[SerializeField]
|
||||
bool m_UseDepthStencilBuffer = true;
|
||||
|
||||
[SerializeField]
|
||||
bool m_UseCameraSortingLayersTexture = false;
|
||||
|
||||
[SerializeField]
|
||||
int m_CameraSortingLayersTextureBound = 0;
|
||||
|
||||
[SerializeField]
|
||||
Downsampling m_CameraSortingLayerDownsamplingMethod = Downsampling.None;
|
||||
|
||||
[SerializeField]
|
||||
uint m_MaxLightRenderTextureCount = 16;
|
||||
|
||||
[SerializeField]
|
||||
uint m_MaxShadowRenderTextureCount = 1;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/Light2D-Shape.shader")]
|
||||
Shader m_ShapeLightShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/Light2D-Shape-Volumetric.shader")]
|
||||
Shader m_ShapeLightVolumeShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/Light2D-Point.shader")]
|
||||
Shader m_PointLightShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/Light2D-Point-Volumetric.shader")]
|
||||
Shader m_PointLightVolumeShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/Utils/Blit.shader")]
|
||||
Shader m_BlitShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/Utils/Sampling.shader")]
|
||||
Shader m_SamplingShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/ShadowGroup2D.shader")]
|
||||
Shader m_ShadowGroupShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/2D/Shadow2DRemoveSelf.shader")]
|
||||
Shader m_RemoveSelfShadowShader = null;
|
||||
|
||||
[SerializeField, Reload("Shaders/Utils/FallbackError.shader")]
|
||||
Shader m_FallbackErrorShader;
|
||||
|
||||
[SerializeField]
|
||||
PostProcessData m_PostProcessData = null;
|
||||
|
||||
[SerializeField, Reload("Runtime/2D/Data/Textures/FalloffLookupTexture.png")]
|
||||
[HideInInspector]
|
||||
private Texture2D m_FallOffLookup = null;
|
||||
|
||||
public float hdrEmulationScale => m_HDREmulationScale;
|
||||
internal float lightRenderTextureScale => m_LightRenderTextureScale;
|
||||
public Light2DBlendStyle[] lightBlendStyles => m_LightBlendStyles;
|
||||
internal bool useDepthStencilBuffer => m_UseDepthStencilBuffer;
|
||||
internal Texture2D fallOffLookup => m_FallOffLookup;
|
||||
internal Shader shapeLightShader => m_ShapeLightShader;
|
||||
internal Shader shapeLightVolumeShader => m_ShapeLightVolumeShader;
|
||||
internal Shader pointLightShader => m_PointLightShader;
|
||||
internal Shader pointLightVolumeShader => m_PointLightVolumeShader;
|
||||
internal Shader blitShader => m_BlitShader;
|
||||
internal Shader samplingShader => m_SamplingShader;
|
||||
internal Shader shadowGroupShader => m_ShadowGroupShader;
|
||||
internal Shader removeSelfShadowShader => m_RemoveSelfShadowShader;
|
||||
internal PostProcessData postProcessData { get => m_PostProcessData; set { m_PostProcessData = value; } }
|
||||
internal TransparencySortMode transparencySortMode => m_TransparencySortMode;
|
||||
internal Vector3 transparencySortAxis => m_TransparencySortAxis;
|
||||
internal uint lightRenderTextureMemoryBudget => m_MaxLightRenderTextureCount;
|
||||
internal uint shadowRenderTextureMemoryBudget => m_MaxShadowRenderTextureCount;
|
||||
internal bool useCameraSortingLayerTexture => m_UseCameraSortingLayersTexture;
|
||||
internal int cameraSortingLayerTextureBound => m_CameraSortingLayersTextureBound;
|
||||
internal Downsampling cameraSortingLayerDownsamplingMethod => m_CameraSortingLayerDownsamplingMethod;
|
||||
|
||||
protected override ScriptableRenderer Create()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
ReloadAllNullProperties();
|
||||
}
|
||||
#endif
|
||||
return new Renderer2D(this);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
for (var i = 0; i < m_LightBlendStyles.Length; ++i)
|
||||
{
|
||||
m_LightBlendStyles[i].renderTargetHandle.Init($"_ShapeLightTexture{i}");
|
||||
}
|
||||
|
||||
normalsRenderTarget.Init("_NormalMap");
|
||||
shadowsRenderTarget.Init("_ShadowTex");
|
||||
|
||||
const int totalMaterials = 256;
|
||||
if (shadowMaterials == null || shadowMaterials.Length == 0)
|
||||
shadowMaterials = new Material[totalMaterials];
|
||||
if (removeSelfShadowMaterials == null || removeSelfShadowMaterials.Length == 0)
|
||||
removeSelfShadowMaterials = new Material[totalMaterials];
|
||||
}
|
||||
|
||||
// transient data
|
||||
internal Dictionary<uint, Material> lightMaterials { get; } = new Dictionary<uint, Material>();
|
||||
internal Material[] shadowMaterials { get; private set; }
|
||||
internal Material[] removeSelfShadowMaterials { get; private set; }
|
||||
|
||||
internal bool isNormalsRenderTargetValid { get; set; }
|
||||
internal float normalsRenderTargetScale { get; set; }
|
||||
internal RenderTargetHandle normalsRenderTarget;
|
||||
internal RenderTargetHandle shadowsRenderTarget;
|
||||
internal RenderTargetHandle cameraSortingLayerRenderTarget;
|
||||
|
||||
// this shouldn've been in RenderingData along with other cull results
|
||||
internal ILight2DCullResult lightCullResult { get; set; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
public partial class Renderer2DData
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[SerializeField]
|
||||
Renderer2DDefaultMaterialType m_DefaultMaterialType = Renderer2DDefaultMaterialType.Lit;
|
||||
|
||||
[SerializeField, Reload("Runtime/Materials/Sprite-Lit-Default.mat")]
|
||||
Material m_DefaultCustomMaterial = null;
|
||||
|
||||
[SerializeField, Reload("Runtime/Materials/Sprite-Lit-Default.mat")]
|
||||
Material m_DefaultLitMaterial = null;
|
||||
|
||||
[SerializeField, Reload("Runtime/Materials/Sprite-Unlit-Default.mat")]
|
||||
Material m_DefaultUnlitMaterial = null;
|
||||
|
||||
internal override Shader GetDefaultShader()
|
||||
{
|
||||
return Shader.Find("Universal Render Pipeline/2D/Sprite-Lit-Default");
|
||||
}
|
||||
|
||||
internal override Material GetDefaultMaterial(DefaultMaterialType materialType)
|
||||
{
|
||||
if (materialType == DefaultMaterialType.Sprite || materialType == DefaultMaterialType.Particle)
|
||||
{
|
||||
if (m_DefaultMaterialType == Renderer2DDefaultMaterialType.Lit)
|
||||
return m_DefaultLitMaterial;
|
||||
else if (m_DefaultMaterialType == Renderer2DDefaultMaterialType.Unlit)
|
||||
return m_DefaultUnlitMaterial;
|
||||
else
|
||||
return m_DefaultCustomMaterial;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnEnableInEditor()
|
||||
{
|
||||
// Provide a list of suggested texture property names to Sprite Editor via EditorPrefs.
|
||||
const string suggestedNamesKey = "SecondarySpriteTexturePropertyNames";
|
||||
const string maskTex = "_MaskTex";
|
||||
const string normalMap = "_NormalMap";
|
||||
string suggestedNamesPrefs = EditorPrefs.GetString(suggestedNamesKey);
|
||||
|
||||
if (string.IsNullOrEmpty(suggestedNamesPrefs))
|
||||
EditorPrefs.SetString(suggestedNamesKey, maskTex + "," + normalMap);
|
||||
else
|
||||
{
|
||||
if (!suggestedNamesPrefs.Contains(maskTex))
|
||||
suggestedNamesPrefs += ("," + maskTex);
|
||||
|
||||
if (!suggestedNamesPrefs.Contains(normalMap))
|
||||
suggestedNamesPrefs += ("," + normalMap);
|
||||
|
||||
EditorPrefs.SetString(suggestedNamesKey, suggestedNamesPrefs);
|
||||
}
|
||||
|
||||
ReloadAllNullProperties();
|
||||
}
|
||||
|
||||
private void ReloadAllNullProperties()
|
||||
{
|
||||
ResourceReloader.TryReloadAllNullIn(this, UniversalRenderPipelineAsset.packagePath);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (m_LightBlendStyles != null)
|
||||
{
|
||||
for (int i = 0; i < m_LightBlendStyles.Length; ++i)
|
||||
{
|
||||
ref var blendStyle = ref m_LightBlendStyles[i];
|
||||
|
||||
// Custom blend mode (99) now falls back to Multiply.
|
||||
if ((int)blendStyle.blendMode == 99)
|
||||
blendStyle.blendMode = Light2DBlendStyle.BlendMode.Multiply;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_LightBlendStyles = new Light2DBlendStyle[4];
|
||||
|
||||
m_LightBlendStyles[0].name = "Multiply";
|
||||
m_LightBlendStyles[0].blendMode = Light2DBlendStyle.BlendMode.Multiply;
|
||||
|
||||
m_LightBlendStyles[1].name = "Additive";
|
||||
m_LightBlendStyles[1].blendMode = Light2DBlendStyle.BlendMode.Additive;
|
||||
|
||||
m_LightBlendStyles[2].name = "Multiply with Mask";
|
||||
m_LightBlendStyles[2].blendMode = Light2DBlendStyle.BlendMode.Multiply;
|
||||
m_LightBlendStyles[2].maskTextureChannel = Light2DBlendStyle.TextureChannel.R;
|
||||
|
||||
m_LightBlendStyles[3].name = "Additive with Mask";
|
||||
m_LightBlendStyles[3].blendMode = Light2DBlendStyle.BlendMode.Additive;
|
||||
m_LightBlendStyles[3].maskTextureChannel = Light2DBlendStyle.TextureChannel.R;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
[AddComponentMenu("Rendering/2D/Composite Shadow Caster 2D (Experimental)")]
|
||||
[ExecuteInEditMode]
|
||||
public class CompositeShadowCaster2D : ShadowCasterGroup2D
|
||||
{
|
||||
protected void OnEnable()
|
||||
{
|
||||
ShadowCasterGroup2DManager.AddGroup(this);
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
ShadowCasterGroup2DManager.RemoveGroup(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
/// <summary>
|
||||
/// Class <c>ShadowCaster2D</c> contains properties used for shadow casting
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("Rendering/2D/Shadow Caster 2D (Experimental)")]
|
||||
public class ShadowCaster2D : ShadowCasterGroup2D
|
||||
{
|
||||
[SerializeField] bool m_HasRenderer = false;
|
||||
[SerializeField] bool m_UseRendererSilhouette = true;
|
||||
[SerializeField] bool m_CastsShadows = true;
|
||||
[SerializeField] bool m_SelfShadows = false;
|
||||
[SerializeField] int[] m_ApplyToSortingLayers = null;
|
||||
[SerializeField] Vector3[] m_ShapePath = null;
|
||||
[SerializeField] int m_ShapePathHash = 0;
|
||||
[SerializeField] Mesh m_Mesh;
|
||||
[SerializeField] int m_InstanceId;
|
||||
|
||||
internal ShadowCasterGroup2D m_ShadowCasterGroup = null;
|
||||
internal ShadowCasterGroup2D m_PreviousShadowCasterGroup = null;
|
||||
|
||||
internal Mesh mesh => m_Mesh;
|
||||
internal Vector3[] shapePath => m_ShapePath;
|
||||
internal int shapePathHash { get { return m_ShapePathHash; } set { m_ShapePathHash = value; } }
|
||||
|
||||
int m_PreviousShadowGroup = 0;
|
||||
bool m_PreviousCastsShadows = true;
|
||||
int m_PreviousPathHash = 0;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
public bool useRendererSilhouette
|
||||
{
|
||||
set { m_UseRendererSilhouette = value; }
|
||||
get { return m_UseRendererSilhouette && m_HasRenderer; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, the shadow casting shape is included as part of the shadow. If false, the shadow casting shape is excluded from the shadow.
|
||||
/// </summary>
|
||||
public bool selfShadows
|
||||
{
|
||||
set { m_SelfShadows = value; }
|
||||
get { return m_SelfShadows; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies if shadows will be cast.
|
||||
/// </summary>
|
||||
public bool castsShadows
|
||||
{
|
||||
set { m_CastsShadows = value; }
|
||||
get { return m_CastsShadows; }
|
||||
}
|
||||
|
||||
static int[] SetDefaultSortingLayers()
|
||||
{
|
||||
int layerCount = SortingLayer.layers.Length;
|
||||
int[] allLayers = new int[layerCount];
|
||||
|
||||
for (int layerIndex = 0; layerIndex < layerCount; layerIndex++)
|
||||
{
|
||||
allLayers[layerIndex] = SortingLayer.layers[layerIndex].id;
|
||||
}
|
||||
|
||||
return allLayers;
|
||||
}
|
||||
|
||||
internal bool IsShadowedLayer(int layer)
|
||||
{
|
||||
return m_ApplyToSortingLayers != null ? Array.IndexOf(m_ApplyToSortingLayers, layer) >= 0 : false;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (m_ApplyToSortingLayers == null)
|
||||
m_ApplyToSortingLayers = SetDefaultSortingLayers();
|
||||
|
||||
Bounds bounds = new Bounds(transform.position, Vector3.one);
|
||||
|
||||
Renderer renderer = GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
bounds = renderer.bounds;
|
||||
}
|
||||
#if USING_PHYSICS2D_MODULE
|
||||
else
|
||||
{
|
||||
Collider2D collider = GetComponent<Collider2D>();
|
||||
if (collider != null)
|
||||
bounds = collider.bounds;
|
||||
}
|
||||
#endif
|
||||
Vector3 inverseScale = Vector3.zero;
|
||||
Vector3 relOffset = transform.position;
|
||||
|
||||
if (transform.lossyScale.x != 0 && transform.lossyScale.y != 0)
|
||||
{
|
||||
inverseScale = new Vector3(1 / transform.lossyScale.x, 1 / transform.lossyScale.y);
|
||||
relOffset = new Vector3(inverseScale.x * -transform.position.x, inverseScale.y * -transform.position.y);
|
||||
}
|
||||
|
||||
if (m_ShapePath == null || m_ShapePath.Length == 0)
|
||||
{
|
||||
m_ShapePath = new Vector3[]
|
||||
{
|
||||
relOffset + new Vector3(inverseScale.x * bounds.min.x, inverseScale.y * bounds.min.y),
|
||||
relOffset + new Vector3(inverseScale.x * bounds.min.x, inverseScale.y * bounds.max.y),
|
||||
relOffset + new Vector3(inverseScale.x * bounds.max.x, inverseScale.y * bounds.max.y),
|
||||
relOffset + new Vector3(inverseScale.x * bounds.max.x, inverseScale.y * bounds.min.y),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnEnable()
|
||||
{
|
||||
if (m_Mesh == null || m_InstanceId != GetInstanceID())
|
||||
{
|
||||
m_Mesh = new Mesh();
|
||||
ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
|
||||
m_InstanceId = GetInstanceID();
|
||||
}
|
||||
|
||||
m_ShadowCasterGroup = null;
|
||||
}
|
||||
|
||||
protected void OnDisable()
|
||||
{
|
||||
ShadowCasterGroup2DManager.RemoveFromShadowCasterGroup(this, m_ShadowCasterGroup);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Renderer renderer;
|
||||
m_HasRenderer = TryGetComponent<Renderer>(out renderer);
|
||||
|
||||
bool rebuildMesh = LightUtility.CheckForChange(m_ShapePathHash, ref m_PreviousPathHash);
|
||||
if (rebuildMesh)
|
||||
ShadowUtility.GenerateShadowMesh(m_Mesh, m_ShapePath);
|
||||
|
||||
m_PreviousShadowCasterGroup = m_ShadowCasterGroup;
|
||||
bool addedToNewGroup = ShadowCasterGroup2DManager.AddToShadowCasterGroup(this, ref m_ShadowCasterGroup);
|
||||
if (addedToNewGroup && m_ShadowCasterGroup != null)
|
||||
{
|
||||
if (m_PreviousShadowCasterGroup == this)
|
||||
ShadowCasterGroup2DManager.RemoveGroup(this);
|
||||
|
||||
ShadowCasterGroup2DManager.RemoveFromShadowCasterGroup(this, m_PreviousShadowCasterGroup);
|
||||
if (m_ShadowCasterGroup == this)
|
||||
ShadowCasterGroup2DManager.AddGroup(this);
|
||||
}
|
||||
|
||||
if (LightUtility.CheckForChange(m_ShadowGroup, ref m_PreviousShadowGroup))
|
||||
{
|
||||
ShadowCasterGroup2DManager.RemoveGroup(this);
|
||||
ShadowCasterGroup2DManager.AddGroup(this);
|
||||
}
|
||||
|
||||
if (LightUtility.CheckForChange(m_CastsShadows, ref m_PreviousCastsShadows))
|
||||
{
|
||||
if (m_CastsShadows)
|
||||
ShadowCasterGroup2DManager.AddGroup(this);
|
||||
else
|
||||
ShadowCasterGroup2DManager.RemoveGroup(this);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void Reset()
|
||||
{
|
||||
Awake();
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
public abstract class ShadowCasterGroup2D : MonoBehaviour
|
||||
{
|
||||
[SerializeField] internal int m_ShadowGroup = 0;
|
||||
List<ShadowCaster2D> m_ShadowCasters;
|
||||
|
||||
public List<ShadowCaster2D> GetShadowCasters() { return m_ShadowCasters; }
|
||||
|
||||
public int GetShadowGroup() { return m_ShadowGroup; }
|
||||
|
||||
public void RegisterShadowCaster2D(ShadowCaster2D shadowCaster2D)
|
||||
{
|
||||
if (m_ShadowCasters == null)
|
||||
m_ShadowCasters = new List<ShadowCaster2D>();
|
||||
|
||||
m_ShadowCasters.Add(shadowCaster2D);
|
||||
}
|
||||
|
||||
public void UnregisterShadowCaster2D(ShadowCaster2D shadowCaster2D)
|
||||
{
|
||||
if (m_ShadowCasters != null)
|
||||
m_ShadowCasters.Remove(shadowCaster2D);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal class ShadowCasterGroup2DManager
|
||||
{
|
||||
static List<ShadowCasterGroup2D> s_ShadowCasterGroups = null;
|
||||
|
||||
public static List<ShadowCasterGroup2D> shadowCasterGroups { get { return s_ShadowCasterGroups; } }
|
||||
|
||||
|
||||
public static void AddShadowCasterGroupToList(ShadowCasterGroup2D shadowCaster, List<ShadowCasterGroup2D> list)
|
||||
{
|
||||
int positionToInsert = 0;
|
||||
for (positionToInsert = 0; positionToInsert < list.Count; positionToInsert++)
|
||||
{
|
||||
if (shadowCaster.GetShadowGroup() == list[positionToInsert].GetShadowGroup())
|
||||
break;
|
||||
}
|
||||
|
||||
list.Insert(positionToInsert, shadowCaster);
|
||||
}
|
||||
|
||||
public static void RemoveShadowCasterGroupFromList(ShadowCasterGroup2D shadowCaster, List<ShadowCasterGroup2D> list)
|
||||
{
|
||||
list.Remove(shadowCaster);
|
||||
}
|
||||
|
||||
static CompositeShadowCaster2D FindTopMostCompositeShadowCaster(ShadowCaster2D shadowCaster)
|
||||
{
|
||||
CompositeShadowCaster2D retGroup = null;
|
||||
|
||||
Transform transformToCheck = shadowCaster.transform.parent;
|
||||
while (transformToCheck != null)
|
||||
{
|
||||
CompositeShadowCaster2D currentGroup;
|
||||
if (transformToCheck.TryGetComponent<CompositeShadowCaster2D>(out currentGroup))
|
||||
retGroup = currentGroup;
|
||||
|
||||
transformToCheck = transformToCheck.parent;
|
||||
}
|
||||
|
||||
return retGroup;
|
||||
}
|
||||
|
||||
public static bool AddToShadowCasterGroup(ShadowCaster2D shadowCaster, ref ShadowCasterGroup2D shadowCasterGroup)
|
||||
{
|
||||
ShadowCasterGroup2D newShadowCasterGroup = FindTopMostCompositeShadowCaster(shadowCaster) as ShadowCasterGroup2D;
|
||||
|
||||
if (newShadowCasterGroup == null)
|
||||
newShadowCasterGroup = shadowCaster.GetComponent<ShadowCaster2D>();
|
||||
|
||||
if (newShadowCasterGroup != null && shadowCasterGroup != newShadowCasterGroup)
|
||||
{
|
||||
newShadowCasterGroup.RegisterShadowCaster2D(shadowCaster);
|
||||
shadowCasterGroup = newShadowCasterGroup;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveFromShadowCasterGroup(ShadowCaster2D shadowCaster, ShadowCasterGroup2D shadowCasterGroup)
|
||||
{
|
||||
if (shadowCasterGroup != null)
|
||||
shadowCasterGroup.UnregisterShadowCaster2D(shadowCaster);
|
||||
}
|
||||
|
||||
public static void AddGroup(ShadowCasterGroup2D group)
|
||||
{
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
if (s_ShadowCasterGroups == null)
|
||||
s_ShadowCasterGroups = new List<ShadowCasterGroup2D>();
|
||||
|
||||
AddShadowCasterGroupToList(group, s_ShadowCasterGroups);
|
||||
}
|
||||
|
||||
public static void RemoveGroup(ShadowCasterGroup2D group)
|
||||
{
|
||||
if (group != null && s_ShadowCasterGroups != null)
|
||||
RemoveShadowCasterGroupFromList(group, s_ShadowCasterGroups);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,181 @@
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal static class ShadowRendering
|
||||
{
|
||||
private static readonly int k_LightPosID = Shader.PropertyToID("_LightPos");
|
||||
private static readonly int k_ShadowStencilGroupID = Shader.PropertyToID("_ShadowStencilGroup");
|
||||
private static readonly int k_ShadowIntensityID = Shader.PropertyToID("_ShadowIntensity");
|
||||
private static readonly int k_ShadowVolumeIntensityID = Shader.PropertyToID("_ShadowVolumeIntensity");
|
||||
private static readonly int k_ShadowRadiusID = Shader.PropertyToID("_ShadowRadius");
|
||||
|
||||
private static RenderTargetHandle[] m_RenderTargets = null;
|
||||
public static uint maxTextureCount { get; private set; }
|
||||
|
||||
public static void InitializeBudget(uint maxTextureCount)
|
||||
{
|
||||
if (m_RenderTargets == null || m_RenderTargets.Length != maxTextureCount)
|
||||
{
|
||||
m_RenderTargets = new RenderTargetHandle[maxTextureCount];
|
||||
ShadowRendering.maxTextureCount = maxTextureCount;
|
||||
|
||||
for (int i = 0; i < maxTextureCount; i++)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
m_RenderTargets[i].id = Shader.PropertyToID($"ShadowTex_{i}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateShadowRenderTexture(IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmdBuffer, int shadowIndex)
|
||||
{
|
||||
CreateShadowRenderTexture(pass, m_RenderTargets[shadowIndex], renderingData, cmdBuffer);
|
||||
}
|
||||
|
||||
public static void PrerenderShadows(IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmdBuffer, int layerToRender, Light2D light, int shadowIndex, float shadowIntensity)
|
||||
{
|
||||
// Render the shadows for this light
|
||||
RenderShadows(pass, renderingData, cmdBuffer, layerToRender, light, shadowIntensity, m_RenderTargets[shadowIndex].Identifier());
|
||||
}
|
||||
|
||||
public static void SetGlobalShadowTexture(CommandBuffer cmdBuffer, Light2D light, int shadowIndex)
|
||||
{
|
||||
cmdBuffer.SetGlobalTexture("_ShadowTex", m_RenderTargets[shadowIndex].Identifier());
|
||||
cmdBuffer.SetGlobalFloat(k_ShadowIntensityID, 1 - light.shadowIntensity);
|
||||
cmdBuffer.SetGlobalFloat(k_ShadowVolumeIntensityID, 1 - light.shadowVolumeIntensity);
|
||||
}
|
||||
|
||||
public static void DisableGlobalShadowTexture(CommandBuffer cmdBuffer)
|
||||
{
|
||||
cmdBuffer.SetGlobalFloat(k_ShadowIntensityID, 1);
|
||||
cmdBuffer.SetGlobalFloat(k_ShadowVolumeIntensityID, 1);
|
||||
}
|
||||
|
||||
private static void CreateShadowRenderTexture(IRenderPass2D pass, RenderTargetHandle rtHandle, RenderingData renderingData, CommandBuffer cmdBuffer)
|
||||
{
|
||||
var renderTextureScale = Mathf.Clamp(pass.rendererData.lightRenderTextureScale, 0.01f, 1.0f);
|
||||
var width = (int)(renderingData.cameraData.cameraTargetDescriptor.width * renderTextureScale);
|
||||
var height = (int)(renderingData.cameraData.cameraTargetDescriptor.height * renderTextureScale);
|
||||
|
||||
var descriptor = new RenderTextureDescriptor(width, height);
|
||||
descriptor.useMipMap = false;
|
||||
descriptor.autoGenerateMips = false;
|
||||
descriptor.depthBufferBits = 24;
|
||||
descriptor.graphicsFormat = GraphicsFormat.R8G8B8A8_UNorm;
|
||||
descriptor.msaaSamples = 1;
|
||||
descriptor.dimension = TextureDimension.Tex2D;
|
||||
|
||||
cmdBuffer.GetTemporaryRT(rtHandle.id, descriptor, FilterMode.Bilinear);
|
||||
}
|
||||
|
||||
public static void ReleaseShadowRenderTexture(CommandBuffer cmdBuffer, int shadowIndex)
|
||||
{
|
||||
cmdBuffer.ReleaseTemporaryRT(m_RenderTargets[shadowIndex].id);
|
||||
}
|
||||
|
||||
private static Material GetShadowMaterial(this Renderer2DData rendererData, int index)
|
||||
{
|
||||
var shadowMaterialIndex = index % 255;
|
||||
if (rendererData.shadowMaterials[shadowMaterialIndex] == null)
|
||||
{
|
||||
rendererData.shadowMaterials[shadowMaterialIndex] = CoreUtils.CreateEngineMaterial(rendererData.shadowGroupShader);
|
||||
rendererData.shadowMaterials[shadowMaterialIndex].SetFloat(k_ShadowStencilGroupID, index);
|
||||
}
|
||||
|
||||
return rendererData.shadowMaterials[shadowMaterialIndex];
|
||||
}
|
||||
|
||||
private static Material GetRemoveSelfShadowMaterial(this Renderer2DData rendererData, int index)
|
||||
{
|
||||
var shadowMaterialIndex = index % 255;
|
||||
if (rendererData.removeSelfShadowMaterials[shadowMaterialIndex] == null)
|
||||
{
|
||||
rendererData.removeSelfShadowMaterials[shadowMaterialIndex] = CoreUtils.CreateEngineMaterial(rendererData.removeSelfShadowShader);
|
||||
rendererData.removeSelfShadowMaterials[shadowMaterialIndex].SetFloat(k_ShadowStencilGroupID, index);
|
||||
}
|
||||
|
||||
return rendererData.removeSelfShadowMaterials[shadowMaterialIndex];
|
||||
}
|
||||
|
||||
public static void RenderShadows(IRenderPass2D pass, RenderingData renderingData, CommandBuffer cmdBuffer, int layerToRender, Light2D light, float shadowIntensity, RenderTargetIdentifier renderTexture)
|
||||
{
|
||||
cmdBuffer.SetRenderTarget(renderTexture, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.DontCare);
|
||||
cmdBuffer.ClearRenderTarget(true, true, Color.black); // clear stencil
|
||||
|
||||
var shadowRadius = 1.42f * light.boundingSphere.radius;
|
||||
|
||||
cmdBuffer.SetGlobalVector(k_LightPosID, light.transform.position);
|
||||
cmdBuffer.SetGlobalFloat(k_ShadowRadiusID, shadowRadius);
|
||||
|
||||
var shadowMaterial = pass.rendererData.GetShadowMaterial(1);
|
||||
var removeSelfShadowMaterial = pass.rendererData.GetRemoveSelfShadowMaterial(1);
|
||||
var shadowCasterGroups = ShadowCasterGroup2DManager.shadowCasterGroups;
|
||||
if (shadowCasterGroups != null && shadowCasterGroups.Count > 0)
|
||||
{
|
||||
var previousShadowGroupIndex = -1;
|
||||
var incrementingGroupIndex = 0;
|
||||
for (var group = 0; group < shadowCasterGroups.Count; group++)
|
||||
{
|
||||
var shadowCasterGroup = shadowCasterGroups[group];
|
||||
var shadowCasters = shadowCasterGroup.GetShadowCasters();
|
||||
|
||||
var shadowGroupIndex = shadowCasterGroup.GetShadowGroup();
|
||||
if (LightUtility.CheckForChange(shadowGroupIndex, ref previousShadowGroupIndex) || shadowGroupIndex == 0)
|
||||
{
|
||||
incrementingGroupIndex++;
|
||||
shadowMaterial = pass.rendererData.GetShadowMaterial(incrementingGroupIndex);
|
||||
removeSelfShadowMaterial = pass.rendererData.GetRemoveSelfShadowMaterial(incrementingGroupIndex);
|
||||
}
|
||||
|
||||
if (shadowCasters != null)
|
||||
{
|
||||
// Draw the shadow casting group first, then draw the silhouettes..
|
||||
for (var i = 0; i < shadowCasters.Count; i++)
|
||||
{
|
||||
var shadowCaster = shadowCasters[i];
|
||||
|
||||
if (shadowCaster != null && shadowMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
|
||||
{
|
||||
if (shadowCaster.castsShadows)
|
||||
cmdBuffer.DrawMesh(shadowCaster.mesh, shadowCaster.transform.localToWorldMatrix, shadowMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < shadowCasters.Count; i++)
|
||||
{
|
||||
var shadowCaster = shadowCasters[i];
|
||||
|
||||
if (shadowCaster != null && shadowMaterial != null && shadowCaster.IsShadowedLayer(layerToRender))
|
||||
{
|
||||
if (shadowCaster.useRendererSilhouette)
|
||||
{
|
||||
var renderer = shadowCaster.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
if (!shadowCaster.selfShadows)
|
||||
cmdBuffer.DrawRenderer(renderer, removeSelfShadowMaterial);
|
||||
else
|
||||
cmdBuffer.DrawRenderer(renderer, shadowMaterial, 0, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!shadowCaster.selfShadows)
|
||||
{
|
||||
var meshMat = shadowCaster.transform.localToWorldMatrix;
|
||||
cmdBuffer.DrawMesh(shadowCaster.mesh, meshMat, removeSelfShadowMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine.Experimental.Rendering.Universal.LibTessDotNet;
|
||||
|
||||
|
||||
namespace UnityEngine.Experimental.Rendering.Universal
|
||||
{
|
||||
internal class ShadowUtility
|
||||
{
|
||||
internal struct Edge : IComparable<Edge>
|
||||
{
|
||||
public int vertexIndex0;
|
||||
public int vertexIndex1;
|
||||
public Vector4 tangent;
|
||||
private bool compareReversed; // This is done so that edge AB can equal edge BA
|
||||
|
||||
public void AssignVertexIndices(int vi0, int vi1)
|
||||
{
|
||||
vertexIndex0 = vi0;
|
||||
vertexIndex1 = vi1;
|
||||
compareReversed = vi0 > vi1;
|
||||
}
|
||||
|
||||
public int Compare(Edge a, Edge b)
|
||||
{
|
||||
int adjustedVertexIndex0A = a.compareReversed ? a.vertexIndex1 : a.vertexIndex0;
|
||||
int adjustedVertexIndex1A = a.compareReversed ? a.vertexIndex0 : a.vertexIndex1;
|
||||
int adjustedVertexIndex0B = b.compareReversed ? b.vertexIndex1 : b.vertexIndex0;
|
||||
int adjustedVertexIndex1B = b.compareReversed ? b.vertexIndex0 : b.vertexIndex1;
|
||||
|
||||
// Sort first by VI0 then by VI1
|
||||
int deltaVI0 = adjustedVertexIndex0A - adjustedVertexIndex0B;
|
||||
int deltaVI1 = adjustedVertexIndex1A - adjustedVertexIndex1B;
|
||||
|
||||
if (deltaVI0 == 0)
|
||||
return deltaVI1;
|
||||
else
|
||||
return deltaVI0;
|
||||
}
|
||||
|
||||
public int CompareTo(Edge edgeToCompare)
|
||||
{
|
||||
return Compare(this, edgeToCompare);
|
||||
}
|
||||
}
|
||||
|
||||
static Edge CreateEdge(int triangleIndexA, int triangleIndexB, List<Vector3> vertices, List<int> triangles)
|
||||
{
|
||||
Edge retEdge = new Edge();
|
||||
|
||||
retEdge.AssignVertexIndices(triangles[triangleIndexA], triangles[triangleIndexB]);
|
||||
|
||||
Vector3 vertex0 = vertices[retEdge.vertexIndex0];
|
||||
vertex0.z = 0;
|
||||
Vector3 vertex1 = vertices[retEdge.vertexIndex1];
|
||||
vertex1.z = 0;
|
||||
|
||||
Vector3 edgeDir = Vector3.Normalize(vertex1 - vertex0);
|
||||
retEdge.tangent = Vector3.Cross(-Vector3.forward, edgeDir);
|
||||
|
||||
return retEdge;
|
||||
}
|
||||
|
||||
static void PopulateEdgeArray(List<Vector3> vertices, List<int> triangles, List<Edge> edges)
|
||||
{
|
||||
for (int triangleIndex = 0; triangleIndex < triangles.Count; triangleIndex += 3)
|
||||
{
|
||||
edges.Add(CreateEdge(triangleIndex, triangleIndex + 1, vertices, triangles));
|
||||
edges.Add(CreateEdge(triangleIndex + 1, triangleIndex + 2, vertices, triangles));
|
||||
edges.Add(CreateEdge(triangleIndex + 2, triangleIndex, vertices, triangles));
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsOutsideEdge(int edgeIndex, List<Edge> edgesToProcess)
|
||||
{
|
||||
int previousIndex = edgeIndex - 1;
|
||||
int nextIndex = edgeIndex + 1;
|
||||
int numberOfEdges = edgesToProcess.Count;
|
||||
Edge currentEdge = edgesToProcess[edgeIndex];
|
||||
|
||||
return (previousIndex < 0 || (currentEdge.CompareTo(edgesToProcess[edgeIndex - 1]) != 0)) && (nextIndex >= numberOfEdges || (currentEdge.CompareTo(edgesToProcess[edgeIndex + 1]) != 0));
|
||||
}
|
||||
|
||||
static void SortEdges(List<Edge> edgesToProcess)
|
||||
{
|
||||
edgesToProcess.Sort();
|
||||
}
|
||||
|
||||
static void CreateShadowTriangles(List<Vector3> vertices, List<Color> colors, List<int> triangles, List<Vector4> tangents, List<Edge> edges)
|
||||
{
|
||||
for (int edgeIndex = 0; edgeIndex < edges.Count; edgeIndex++)
|
||||
{
|
||||
if (IsOutsideEdge(edgeIndex, edges))
|
||||
{
|
||||
Edge edge = edges[edgeIndex];
|
||||
tangents[edge.vertexIndex1] = -edge.tangent;
|
||||
|
||||
int newVertexIndex = vertices.Count;
|
||||
vertices.Add(vertices[edge.vertexIndex0]);
|
||||
colors.Add(colors[edge.vertexIndex0]);
|
||||
|
||||
tangents.Add(-edge.tangent);
|
||||
|
||||
triangles.Add(edge.vertexIndex0);
|
||||
triangles.Add(newVertexIndex);
|
||||
triangles.Add(edge.vertexIndex1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static object InterpCustomVertexData(Vec3 position, object[] data, float[] weights)
|
||||
{
|
||||
return data[0];
|
||||
}
|
||||
|
||||
static void InitializeTangents(int tangentsToAdd, List<Vector4> tangents)
|
||||
{
|
||||
for (int i = 0; i < tangentsToAdd; i++)
|
||||
tangents.Add(Vector4.zero);
|
||||
}
|
||||
|
||||
public static void GenerateShadowMesh(Mesh mesh, Vector3[] shapePath)
|
||||
{
|
||||
List<Vector3> vertices = new List<Vector3>();
|
||||
List<int> triangles = new List<int>();
|
||||
List<Vector4> tangents = new List<Vector4>();
|
||||
List<Color> extrusion = new List<Color>();
|
||||
|
||||
// Create interior geometry
|
||||
int pointCount = shapePath.Length;
|
||||
var inputs = new ContourVertex[2 * pointCount];
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
Color extrusionData = new Color(shapePath[i].x, shapePath[i].y, shapePath[i].x, shapePath[i].y);
|
||||
int nextPoint = (i + 1) % pointCount;
|
||||
inputs[2 * i] = new ContourVertex() { Position = new Vec3() { X = shapePath[i].x, Y = shapePath[i].y, Z = 0 }, Data = extrusionData };
|
||||
|
||||
extrusionData = new Color(shapePath[i].x, shapePath[i].y, shapePath[nextPoint].x, shapePath[nextPoint].y);
|
||||
Vector2 midPoint = 0.5f * (shapePath[i] + shapePath[nextPoint]);
|
||||
inputs[2 * i + 1] = new ContourVertex() { Position = new Vec3() { X = midPoint.x, Y = midPoint.y, Z = 0}, Data = extrusionData };
|
||||
}
|
||||
|
||||
Tess tessI = new Tess();
|
||||
tessI.AddContour(inputs, ContourOrientation.Original);
|
||||
tessI.Tessellate(WindingRule.EvenOdd, ElementType.Polygons, 3, InterpCustomVertexData);
|
||||
|
||||
var indicesI = tessI.Elements.Select(i => i).ToArray();
|
||||
var verticesI = tessI.Vertices.Select(v => new Vector3(v.Position.X, v.Position.Y, 0)).ToArray();
|
||||
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();
|
||||
|
||||
vertices.AddRange(verticesI);
|
||||
triangles.AddRange(indicesI);
|
||||
extrusion.AddRange(extrusionI);
|
||||
|
||||
InitializeTangents(vertices.Count, tangents);
|
||||
|
||||
List<Edge> edges = new List<Edge>();
|
||||
PopulateEdgeArray(vertices, triangles, edges);
|
||||
SortEdges(edges);
|
||||
CreateShadowTriangles(vertices, extrusion, triangles, tangents, edges);
|
||||
|
||||
Color[] finalExtrusion = extrusion.ToArray();
|
||||
Vector3[] finalVertices = vertices.ToArray();
|
||||
int[] finalTriangles = triangles.ToArray();
|
||||
Vector4[] finalTangents = tangents.ToArray();
|
||||
|
||||
mesh.Clear();
|
||||
mesh.vertices = finalVertices;
|
||||
mesh.triangles = finalTriangles;
|
||||
mesh.tangents = finalTangents;
|
||||
mesh.colors = finalExtrusion;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user