Singularity/Library/PackageCache/com.unity.render-pipelines..../Runtime/PostProcessing/LensFlareCommonSRP.cs

1126 lines
54 KiB
C#
Raw Normal View History

2024-05-06 14:45:45 -04:00
namespace UnityEngine.Rendering
{
/// <summary>
/// Common code for all Data-Driven Lens Flare used
/// </summary>
public sealed class LensFlareCommonSRP
{
private static LensFlareCommonSRP m_Instance = null;
private static readonly object m_Padlock = new object();
private static System.Collections.Generic.List<LensFlareComponentSRP> m_Data = new System.Collections.Generic.List<LensFlareComponentSRP>();
/// <summary>
/// Max lens-flares-with-occlusion supported
/// </summary>
public static int maxLensFlareWithOcclusion = 128;
/// <summary>
/// With TAA Occlusion jitter depth, thought frame on HDRP.
/// So we do a "unanimity vote" for occlusion thought 'maxLensFlareWithOcclusionTemporalSample' frame
/// Important to keep this value maximum of 8
/// If this value change that could implies an implementation modification on:
/// com.unity.render-pipelines.high-definition/Runtime/PostProcessing/Shaders/LensFlareMergeOcclusionDataDriven.compute
/// </summary>
public static int maxLensFlareWithOcclusionTemporalSample = 8;
/// <summary>
/// Set to 1 to enable temporal sample merge.
/// Set to 0 to disable temporal sample merge (must support 16 bit textures, and the occlusion merge must be written in the last texel (vertical) of the lens flare texture.
/// </summary>
public static int mergeNeeded = 1;
/// <summary>
/// occlusion texture either provided or created automatically by the SRP for lens flare. (to be created automatically, please set mergeNeeded to 1).
/// Texture width is the max number of lens flares that have occlusion (x axis the lens flare index).
/// y axis is the number of samples (maxLensFlareWithOcclusionTemporalSample) plus the number of merge results.
/// Merge results must be done by the SRP and stored in the [(lens flareIndex), (maxLensFlareWithOcclusionTemporalSample + 1)] coordinate.
/// </summary>
public static RTHandle occlusionRT = null;
private static int frameIdx = 0;
private LensFlareCommonSRP()
{
}
/// <summary>
/// Initialization function which must be called by the SRP.
/// </summary>
static public void Initialize()
{
if (occlusionRT == null && mergeNeeded > 0)
occlusionRT = RTHandles.Alloc(width: maxLensFlareWithOcclusion, height: maxLensFlareWithOcclusionTemporalSample + 1 * mergeNeeded, colorFormat: Experimental.Rendering.GraphicsFormat.R16_SFloat, enableRandomWrite: true, dimension: TextureXR.dimension);
}
/// <summary>
/// Disposal function, must be called by the SRP to release all internal textures.
/// </summary>
static public void Dispose()
{
if (occlusionRT != null)
{
RTHandles.Release(occlusionRT);
occlusionRT = null;
}
}
/// <summary>
/// Current unique instance
/// </summary>
public static LensFlareCommonSRP Instance
{
get
{
if (m_Instance == null)
{
lock (m_Padlock)
{
if (m_Instance == null)
{
m_Instance = new LensFlareCommonSRP();
}
}
}
return m_Instance;
}
}
private System.Collections.Generic.List<LensFlareComponentSRP> Data { get { return LensFlareCommonSRP.m_Data; } }
/// <summary>
/// Return the pool of Lens Flare added
/// </summary>
/// <returns>The Lens Flare Pool</returns>
public System.Collections.Generic.List<LensFlareComponentSRP> GetData()
{
return Data;
}
/// <summary>
/// Check if we have at least one Lens Flare added on the pool
/// </summary>
/// <returns>true if no Lens Flare were added</returns>
public bool IsEmpty()
{
return Data.Count == 0;
}
/// <summary>
/// Add a new lens flare component on the pool.
/// </summary>
/// <param name="newData">The new data added</param>
public void AddData(LensFlareComponentSRP newData)
{
Debug.Assert(Instance == this, "LensFlareCommonSRP can have only one instance");
if (!m_Data.Contains(newData))
{
m_Data.Add(newData);
}
}
/// <summary>
/// Attenuation by Light Shape for Point Light
/// </summary>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationPointLight()
{
return 1.0f;
}
/// <summary>
/// Attenuation by Light Shape for Directional Light
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationDirLight(Vector3 forward, Vector3 wo)
{
return Mathf.Max(Vector3.Dot(forward, wo), 0.0f);
}
/// <summary>
/// Attenuation by Light Shape for Spot Light with Cone Shape
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <param name="spotAngle">The angle of the light's spotlight cone in degrees.</param>
/// <param name="innerSpotPercent01">Get the inner spot radius between 0 and 1.</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationSpotConeLight(Vector3 forward, Vector3 wo, float spotAngle, float innerSpotPercent01)
{
float outerDot = Mathf.Max(Mathf.Cos(0.5f * spotAngle * Mathf.Deg2Rad), 0.0f);
float innerDot = Mathf.Max(Mathf.Cos(0.5f * spotAngle * Mathf.Deg2Rad * innerSpotPercent01), 0.0f);
float dot = Mathf.Max(Vector3.Dot(forward, wo), 0.0f);
return Mathf.Clamp01((dot - outerDot) / (innerDot - outerDot));
}
/// <summary>
/// Attenuation by Light Shape for Spot Light with Box Shape
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationSpotBoxLight(Vector3 forward, Vector3 wo)
{
return Mathf.Max(Mathf.Sign(Vector3.Dot(forward, wo)), 0.0f);
}
/// <summary>
/// Attenuation by Light Shape for Spot Light with Pyramid Shape
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationSpotPyramidLight(Vector3 forward, Vector3 wo)
{
return ShapeAttenuationSpotBoxLight(forward, wo);
}
/// <summary>
/// Attenuation by Light Shape for Area Light with Tube Shape
/// </summary>
/// <param name="lightPositionWS">World Space position of the Light</param>
/// <param name="lightSide">Vector pointing to the side (right or left) or the light</param>
/// <param name="lightWidth">Width (half extent) of the tube light</param>
/// <param name="cam">Camera rendering the Tube Light</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationAreaTubeLight(Vector3 lightPositionWS, Vector3 lightSide, float lightWidth, Camera cam)
{
// Ref: https://hal.archives-ouvertes.fr/hal-02155101/document
// Listing 1.6. Analytic line-diffuse integration.
float Fpo(float d, float l)
{
return l / (d * (d * d + l * l)) + Mathf.Atan(l / d) / (d * d);
}
float Fwt(float d, float l)
{
return l * l / (d * (d * d + l * l));
}
Vector3 p1Global = lightPositionWS + lightSide * lightWidth * 0.5f;
Vector3 p2Global = lightPositionWS - lightSide * lightWidth * 0.5f;
Vector3 p1Front = lightPositionWS + cam.transform.right * lightWidth * 0.5f;
Vector3 p2Front = lightPositionWS - cam.transform.right * lightWidth * 0.5f;
Vector3 p1World = cam.transform.InverseTransformPoint(p1Global);
Vector3 p2World = cam.transform.InverseTransformPoint(p2Global);
Vector3 p1WorldFront = cam.transform.InverseTransformPoint(p1Front);
Vector3 p2WorldFront = cam.transform.InverseTransformPoint(p2Front);
float DiffLineIntegral(Vector3 p1, Vector3 p2)
{
float diffIntegral;
// tangent
Vector3 wt = (p2 - p1).normalized;
// clamping
if (p1.z <= 0.0 && p2.z <= 0.0)
{
diffIntegral = 0.0f;
}
else
{
if (p1.z < 0.0)
p1 = (p1 * p2.z - p2 * p1.z) / (+p2.z - p1.z);
if (p2.z < 0.0)
p2 = (-p1 * p2.z + p2 * p1.z) / (-p2.z + p1.z);
// parameterization
float l1 = Vector3.Dot(p1, wt);
float l2 = Vector3.Dot(p2, wt);
// shading point orthonormal projection on the line
Vector3 po = p1 - l1 * wt;
// distance to line
float d = po.magnitude;
// integral
float integral = (Fpo(d, l2) - Fpo(d, l1)) * po.z + (Fwt(d, l2) - Fwt(d, l1)) * wt.z;
diffIntegral = integral / Mathf.PI;
}
return diffIntegral;
}
float frontModulation = DiffLineIntegral(p1WorldFront, p2WorldFront);
float worldModulation = DiffLineIntegral(p1World, p2World);
return frontModulation > 0.0f ? worldModulation / frontModulation : 1.0f;
}
/// <summary>
/// Attenuation by Light Shape for Area Light with Rectangular Shape
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationAreaRectangleLight(Vector3 forward, Vector3 wo)
{
return ShapeAttenuationDirLight(forward, wo);
}
/// <summary>
/// Attenuation by Light Shape for Area Light with Disc Shape
/// </summary>
/// <param name="forward">Forward Vector of Directional Light</param>
/// <param name="wo">Vector pointing to the eye</param>
/// <returns>Attenuation Factor</returns>
static public float ShapeAttenuationAreaDiscLight(Vector3 forward, Vector3 wo)
{
return ShapeAttenuationDirLight(forward, wo);
}
/// <summary>
/// Compute internal parameters needed to render single flare
/// </summary>
/// <param name="screenPos"></param>
/// <param name="translationScale"></param>
/// <param name="rayOff0"></param>
/// <param name="vLocalScreenRatio"></param>
/// <param name="angleDeg"></param>
/// <param name="position"></param>
/// <param name="angularOffset"></param>
/// <param name="positionOffset"></param>
/// <param name="autoRotate"></param>
/// <returns>Parameter used on the shader for _FlareData0</returns>
static public Vector4 GetFlareData0(Vector2 screenPos, Vector2 translationScale, Vector2 rayOff0, Vector2 vLocalScreenRatio, float angleDeg, float position, float angularOffset, Vector2 positionOffset, bool autoRotate)
{
if (!SystemInfo.graphicsUVStartsAtTop)
{
angleDeg *= -1;
positionOffset.y *= -1;
}
float globalCos0 = Mathf.Cos(-angularOffset * Mathf.Deg2Rad);
float globalSin0 = Mathf.Sin(-angularOffset * Mathf.Deg2Rad);
Vector2 rayOff = -translationScale * (screenPos + screenPos * (position - 1.0f));
rayOff = new Vector2(globalCos0 * rayOff.x - globalSin0 * rayOff.y,
globalSin0 * rayOff.x + globalCos0 * rayOff.y);
float rotation = angleDeg;
rotation += 180.0f;
//if (!autoRotate)
//{
// //rotation = Mathf.Abs(rotation) < 1e-4f ? 360.0f : rotation;
//}
//else
if (autoRotate)
{
Vector2 pos = (rayOff.normalized * vLocalScreenRatio) * translationScale;
rotation += -Mathf.Rad2Deg * Mathf.Atan2(pos.y, pos.x);
}
rotation *= Mathf.Deg2Rad;
float localCos0 = Mathf.Cos(-rotation);
float localSin0 = Mathf.Sin(-rotation);
return new Vector4(localCos0, localSin0, positionOffset.x + rayOff0.x * translationScale.x, -positionOffset.y + rayOff0.y * translationScale.y);
}
static Vector2 GetLensFlareRayOffset(Vector2 screenPos, float position, float globalCos0, float globalSin0)
{
Vector2 rayOff = -(screenPos + screenPos * (position - 1.0f));
return new Vector2(globalCos0 * rayOff.x - globalSin0 * rayOff.y,
globalSin0 * rayOff.x + globalCos0 * rayOff.y);
}
static Vector3 WorldToViewport(Camera camera, bool isLocalLight, bool isCameraRelative, Matrix4x4 viewProjMatrix, Vector3 positionWS)
{
if (isLocalLight)
{
return WorldToViewportLocal(isCameraRelative, viewProjMatrix, camera.transform.position, positionWS);
}
else
{
return WorldToViewportDistance(camera, positionWS);
}
}
static Vector3 WorldToViewportLocal(bool isCameraRelative, Matrix4x4 viewProjMatrix, Vector3 cameraPosWS, Vector3 positionWS)
{
Vector3 localPositionWS = positionWS;
if (isCameraRelative)
{
localPositionWS -= cameraPosWS;
}
Vector4 viewportPos4 = viewProjMatrix * localPositionWS;
Vector3 viewportPos = new Vector3(viewportPos4.x, viewportPos4.y, 0f);
viewportPos /= viewportPos4.w;
viewportPos.x = viewportPos.x * 0.5f + 0.5f;
viewportPos.y = viewportPos.y * 0.5f + 0.5f;
viewportPos.y = 1.0f - viewportPos.y;
viewportPos.z = viewportPos4.w;
return viewportPos;
}
static Vector3 WorldToViewportDistance(Camera cam, Vector3 positionWS)
{
Vector4 camPos = cam.worldToCameraMatrix * positionWS;
Vector4 viewportPos4 = cam.projectionMatrix * camPos;
Vector3 viewportPos = new Vector3(viewportPos4.x, viewportPos4.y, 0f);
viewportPos /= viewportPos4.w;
viewportPos.x = viewportPos.x * 0.5f + 0.5f;
viewportPos.y = viewportPos.y * 0.5f + 0.5f;
viewportPos.z = viewportPos4.w;
return viewportPos;
}
/// <summary>
/// Effective Job of drawing the set of Lens Flare registered
/// </summary>
/// <param name="lensFlareShader">Lens Flare material (HDRP or URP shader)</param>
/// <param name="lensFlares">Set of Lens Flare</param>
/// <param name="cam">Camera</param>
/// <param name="actualWidth">Width actually used for rendering after dynamic resolution and XR is applied.</param>
/// <param name="actualHeight">Height actually used for rendering after dynamic resolution and XR is applied.</param>
/// <param name="usePanini">Set if use Panani Projection</param>
/// <param name="paniniDistance">Distance used for Panini projection</param>
/// <param name="paniniCropToFit">CropToFit parameter used for Panini projection</param>
/// <param name="isCameraRelative">Set if camera is relative</param>
/// <param name="cameraPositionWS">Camera World Space position</param>
/// <param name="viewProjMatrix">View Projection Matrix of the current camera</param>
/// <param name="cmd">Command Buffer</param>
/// <param name="taaEnabled">Set if TAA is enabled</param>
/// <param name="_FlareOcclusionTex">ShaderID for the FlareOcclusionTex</param>
/// <param name="_FlareOcclusionIndex">ShaderID for the FlareOcclusionIndex</param>
/// <param name="_FlareTex">ShaderID for the FlareTex</param>
/// <param name="_FlareColorValue">ShaderID for the FlareColor</param>
/// <param name="_FlareData0">ShaderID for the FlareData0</param>
/// <param name="_FlareData1">ShaderID for the FlareData1</param>
/// <param name="_FlareData2">ShaderID for the FlareData2</param>
/// <param name="_FlareData3">ShaderID for the FlareData3</param>
/// <param name="_FlareData4">ShaderID for the FlareData4</param>
static public void ComputeOcclusion(Material lensFlareShader, LensFlareCommonSRP lensFlares, Camera cam,
float actualWidth, float actualHeight,
bool usePanini, float paniniDistance, float paniniCropToFit, bool isCameraRelative,
Vector3 cameraPositionWS,
Matrix4x4 viewProjMatrix,
Rendering.CommandBuffer cmd,
bool taaEnabled,
int _FlareOcclusionTex, int _FlareOcclusionIndex, int _FlareTex, int _FlareColorValue, int _FlareData0, int _FlareData1, int _FlareData2, int _FlareData3, int _FlareData4)
{
Vector2 vScreenRatio;
if (lensFlares.IsEmpty() || occlusionRT == null)
return;
Vector2 screenSize = new Vector2(actualWidth, actualHeight);
float screenRatio = screenSize.x / screenSize.y;
vScreenRatio = new Vector2(screenRatio, 1.0f);
#if UNITY_EDITOR
if (cam.cameraType == CameraType.SceneView)
{
// Determine whether the "Animated Materials" checkbox is checked for the current view.
for (int i = 0; i < UnityEditor.SceneView.sceneViews.Count; i++) // Using a foreach on an ArrayList generates garbage ...
{
var sv = UnityEditor.SceneView.sceneViews[i] as UnityEditor.SceneView;
if (sv.camera == cam && !sv.sceneViewState.flaresEnabled)
{
return;
}
}
}
#endif
Rendering.CoreUtils.SetRenderTarget(cmd, occlusionRT);
if (!taaEnabled)
{
cmd.ClearRenderTarget(false, true, Color.black);
}
float dx = 1.0f / ((float)maxLensFlareWithOcclusion);
float dy = 1.0f / ((float)(maxLensFlareWithOcclusionTemporalSample + 1 * mergeNeeded));
float halfx = 0.5f / ((float)maxLensFlareWithOcclusion);
float halfy = 0.5f / ((float)(maxLensFlareWithOcclusionTemporalSample + 1 * mergeNeeded));
int taaValue = taaEnabled ? 1 : 0;
int occlusionIndex = 0;
foreach (LensFlareComponentSRP comp in lensFlares.GetData())
{
if (comp == null)
continue;
LensFlareDataSRP data = comp.lensFlareData;
if (!comp.enabled ||
!comp.gameObject.activeSelf ||
!comp.gameObject.activeInHierarchy ||
data == null ||
data.elements == null ||
data.elements.Length == 0 ||
!comp.useOcclusion ||
(comp.useOcclusion && comp.sampleCount == 0) ||
comp.intensity <= 0.0f)
continue;
Light light = comp.GetComponent<Light>();
Vector3 positionWS;
Vector3 viewportPos;
bool isDirLight = false;
if (light != null && light.type == LightType.Directional)
{
positionWS = -light.transform.forward * cam.farClipPlane;
isDirLight = true;
}
else
{
positionWS = comp.transform.position;
}
viewportPos = WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS);
if (usePanini && cam == Camera.main)
{
viewportPos = DoPaniniProjection(viewportPos, actualWidth, actualHeight, cam.fieldOfView, paniniCropToFit, paniniDistance);
}
if (viewportPos.z < 0.0f)
continue;
if (!comp.allowOffScreen)
{
if (viewportPos.x < 0.0f || viewportPos.x > 1.0f ||
viewportPos.y < 0.0f || viewportPos.y > 1.0f)
continue;
}
Vector3 diffToObject = positionWS - cameraPositionWS;
float distToObject = diffToObject.magnitude;
float coefDistSample = distToObject / comp.maxAttenuationDistance;
float coefScaleSample = distToObject / comp.maxAttenuationScale;
float distanceAttenuation = !isDirLight && comp.distanceAttenuationCurve.length > 0 ? comp.distanceAttenuationCurve.Evaluate(coefDistSample) : 1.0f;
float scaleByDistance = !isDirLight && comp.scaleByDistanceCurve.length >= 1 ? comp.scaleByDistanceCurve.Evaluate(coefScaleSample) : 1.0f;
Vector3 dir = (cam.transform.position - comp.transform.position).normalized;
Vector3 screenPosZ = WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS + dir * comp.occlusionOffset);
float adjustedOcclusionRadius = isDirLight ? comp.celestialProjectedOcclusionRadius(cam) : comp.occlusionRadius;
Vector2 occlusionRadiusEdgeScreenPos0 = (Vector2)viewportPos;
Vector2 occlusionRadiusEdgeScreenPos1 = (Vector2)WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS + cam.transform.up * adjustedOcclusionRadius);
float occlusionRadius = (occlusionRadiusEdgeScreenPos1 - occlusionRadiusEdgeScreenPos0).magnitude;
cmd.SetGlobalVector(_FlareData1, new Vector4(occlusionRadius, comp.sampleCount, screenPosZ.z, actualHeight / actualWidth));
cmd.EnableShaderKeyword("FLARE_COMPUTE_OCCLUSION");
Vector2 screenPos = new Vector2(2.0f * viewportPos.x - 1.0f, 1.0f - 2.0f * viewportPos.y);
Vector2 radPos = new Vector2(Mathf.Abs(screenPos.x), Mathf.Abs(screenPos.y));
float radius = Mathf.Max(radPos.x, radPos.y); // l1 norm (instead of l2 norm)
float radialsScaleRadius = comp.radialScreenAttenuationCurve.length > 0 ? comp.radialScreenAttenuationCurve.Evaluate(radius) : 1.0f;
float currentIntensity = comp.intensity * radialsScaleRadius * distanceAttenuation;
if (currentIntensity <= 0.0f)
continue;
cmd.SetGlobalVector(_FlareOcclusionIndex, new Vector4(((float)(occlusionIndex)) * dx + halfx, halfy, 0, frameIdx + 1));
float globalCos0 = Mathf.Cos(0.0f);
float globalSin0 = Mathf.Sin(0.0f);
float position = 0.0f;
float usedGradientPosition = Mathf.Clamp01(1.0f - 1e-6f);
cmd.SetGlobalVector(_FlareData3, new Vector4(comp.allowOffScreen ? 1.0f : -1.0f, usedGradientPosition, Mathf.Exp(Mathf.Lerp(0.0f, 4.0f, 1.0f)), 1.0f / 3.0f));
Vector2 rayOff = GetLensFlareRayOffset(screenPos, position, globalCos0, globalSin0);
Vector4 flareData0 = GetFlareData0(screenPos, Vector2.one, rayOff, vScreenRatio, 0.0f, position, 0.0f, Vector2.zero, false);
cmd.SetGlobalVector(_FlareData0, flareData0);
cmd.SetGlobalVector(_FlareData2, new Vector4(screenPos.x, screenPos.y, 0.0f, 0.0f));
cmd.SetViewport(new Rect() { x = occlusionIndex, y = (frameIdx + 1 * mergeNeeded) * taaValue, width = 1, height = 1 });
UnityEngine.Rendering.Blitter.DrawQuad(cmd, lensFlareShader, 4);
++occlusionIndex;
}
++frameIdx;
frameIdx %= maxLensFlareWithOcclusionTemporalSample;
}
/// <summary>
/// Effective Job of drawing the set of Lens Flare registered
/// </summary>
/// <param name="lensFlareShader">Lens Flare material (HDRP or URP shader)</param>
/// <param name="lensFlares">Set of Lens Flare</param>
/// <param name="cam">Camera</param>
/// <param name="actualWidth">Width actually used for rendering after dynamic resolution and XR is applied.</param>
/// <param name="actualHeight">Height actually used for rendering after dynamic resolution and XR is applied.</param>
/// <param name="usePanini">Set if use Panani Projection</param>
/// <param name="paniniDistance">Distance used for Panini projection</param>
/// <param name="paniniCropToFit">CropToFit parameter used for Panini projection</param>
/// <param name="isCameraRelative">Set if camera is relative</param>
/// <param name="cameraPositionWS">Camera World Space position</param>
/// <param name="viewProjMatrix">View Projection Matrix of the current camera</param>
/// <param name="cmd">Command Buffer</param>
/// <param name="colorBuffer">Source Render Target which contains the Color Buffer</param>
/// <param name="GetLensFlareLightAttenuation">Delegate to which return return the Attenuation of the light based on their shape which uses the functions ShapeAttenuation...(...), must reimplemented per SRP</param>
/// <param name="_FlareOcclusionTex">ShaderID for the FlareOcclusionTex</param>
/// <param name="_FlareOcclusionIndex">ShaderID for the FlareOcclusionIndex</param>
/// <param name="_FlareTex">ShaderID for the FlareTex</param>
/// <param name="_FlareColorValue">ShaderID for the FlareColor</param>
/// <param name="_FlareData0">ShaderID for the FlareData0</param>
/// <param name="_FlareData1">ShaderID for the FlareData1</param>
/// <param name="_FlareData2">ShaderID for the FlareData2</param>
/// <param name="_FlareData3">ShaderID for the FlareData3</param>
/// <param name="_FlareData4">ShaderID for the FlareData4</param>
/// <param name="debugView">Debug View which setup black background to see only Lens Flare</param>
static public void DoLensFlareDataDrivenCommon(Material lensFlareShader, LensFlareCommonSRP lensFlares, Camera cam, float actualWidth, float actualHeight,
bool usePanini, float paniniDistance, float paniniCropToFit,
bool isCameraRelative,
Vector3 cameraPositionWS,
Matrix4x4 viewProjMatrix,
Rendering.CommandBuffer cmd,
Rendering.RenderTargetIdentifier colorBuffer,
System.Func<Light, Camera, Vector3, float> GetLensFlareLightAttenuation,
int _FlareOcclusionTex, int _FlareOcclusionIndex, int _FlareTex, int _FlareColorValue, int _FlareData0, int _FlareData1, int _FlareData2, int _FlareData3, int _FlareData4,
bool debugView)
{
Vector2 vScreenRatio;
if (lensFlares.IsEmpty())
return;
Vector2 screenSize = new Vector2(actualWidth, actualHeight);
float screenRatio = screenSize.x / screenSize.y;
vScreenRatio = new Vector2(screenRatio, 1.0f);
Rendering.CoreUtils.SetRenderTarget(cmd, colorBuffer);
cmd.SetViewport(new Rect() { width = screenSize.x, height = screenSize.y });
if (debugView)
{
// Background pitch black to see only the Flares
cmd.ClearRenderTarget(false, true, Color.black);
}
#if UNITY_EDITOR
if (cam.cameraType == CameraType.SceneView)
{
// Determine whether the "Animated Materials" checkbox is checked for the current view.
for (int i = 0; i < UnityEditor.SceneView.sceneViews.Count; i++) // Using a foreach on an ArrayList generates garbage ...
{
var sv = UnityEditor.SceneView.sceneViews[i] as UnityEditor.SceneView;
if (sv.camera == cam && !sv.sceneViewState.flaresEnabled)
{
return;
}
}
}
#endif
int occlusionIndex = 0;
foreach (LensFlareComponentSRP comp in lensFlares.GetData())
{
if (comp == null)
continue;
LensFlareDataSRP data = comp.lensFlareData;
if (!comp.enabled ||
!comp.gameObject.activeSelf ||
!comp.gameObject.activeInHierarchy ||
data == null ||
data.elements == null ||
data.elements.Length == 0 ||
comp.intensity <= 0.0f)
continue;
Light light = comp.GetComponent<Light>();
Vector3 positionWS;
Vector3 viewportPos;
bool isDirLight = false;
if (light != null && light.type == LightType.Directional)
{
positionWS = -light.transform.forward * cam.farClipPlane;
isDirLight = true;
}
else
{
positionWS = comp.transform.position;
}
viewportPos = WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS);
if (usePanini && cam == Camera.main)
{
viewportPos = DoPaniniProjection(viewportPos, actualWidth, actualHeight, cam.fieldOfView, paniniCropToFit, paniniDistance);
}
if (viewportPos.z < 0.0f)
continue;
if (!comp.allowOffScreen)
{
if (viewportPos.x < 0.0f || viewportPos.x > 1.0f ||
viewportPos.y < 0.0f || viewportPos.y > 1.0f)
continue;
}
Vector3 diffToObject = positionWS - cameraPositionWS;
// Check if the light is forward, can be an issue with,
// the math associated to Panini projection
if (Vector3.Dot(cam.transform.forward, diffToObject) < 0.0f)
{
continue;
}
float distToObject = diffToObject.magnitude;
float coefDistSample = distToObject / comp.maxAttenuationDistance;
float coefScaleSample = distToObject / comp.maxAttenuationScale;
float distanceAttenuation = !isDirLight && comp.distanceAttenuationCurve.length > 0 ? comp.distanceAttenuationCurve.Evaluate(coefDistSample) : 1.0f;
float scaleByDistance = !isDirLight && comp.scaleByDistanceCurve.length >= 1 ? comp.scaleByDistanceCurve.Evaluate(coefScaleSample) : 1.0f;
Color globalColorModulation = Color.white;
if (light != null)
{
if (comp.attenuationByLightShape)
globalColorModulation *= GetLensFlareLightAttenuation(light, cam, -diffToObject.normalized);
}
globalColorModulation *= distanceAttenuation;
Vector3 dir = (cam.transform.position - comp.transform.position).normalized;
Vector3 screenPosZ = WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS + dir * comp.occlusionOffset);
float adjustedOcclusionRadius = isDirLight ? comp.celestialProjectedOcclusionRadius(cam) : comp.occlusionRadius;
Vector2 occlusionRadiusEdgeScreenPos0 = (Vector2)viewportPos;
Vector2 occlusionRadiusEdgeScreenPos1 = (Vector2)WorldToViewport(cam, !isDirLight, isCameraRelative, viewProjMatrix, positionWS + cam.transform.up * adjustedOcclusionRadius);
float occlusionRadius = (occlusionRadiusEdgeScreenPos1 - occlusionRadiusEdgeScreenPos0).magnitude;
cmd.SetGlobalVector(_FlareData1, new Vector4(occlusionRadius, comp.sampleCount, screenPosZ.z, actualHeight / actualWidth));
if (comp.useOcclusion)
{
cmd.EnableShaderKeyword("FLARE_OCCLUSION");
}
else
{
cmd.DisableShaderKeyword("FLARE_OCCLUSION");
}
if (occlusionRT != null)
cmd.SetGlobalTexture(_FlareOcclusionTex, occlusionRT);
cmd.SetGlobalVector(_FlareOcclusionIndex, new Vector4((float)occlusionIndex / (float)LensFlareCommonSRP.maxLensFlareWithOcclusion + 0.5f / (float)LensFlareCommonSRP.maxLensFlareWithOcclusion, 0.5f, 0, 0));
if (comp.useOcclusion && comp.sampleCount > 0)
++occlusionIndex;
foreach (LensFlareDataElementSRP element in data.elements)
{
if (element == null ||
element.visible == false ||
(element.lensFlareTexture == null && element.flareType == SRPLensFlareType.Image) ||
element.localIntensity <= 0.0f ||
element.count <= 0 ||
element.localIntensity <= 0.0f)
continue;
Color colorModulation = globalColorModulation;
if (light != null && element.modulateByLightColor)
{
if (light.useColorTemperature)
colorModulation *= light.color * Mathf.CorrelatedColorTemperatureToRGB(light.colorTemperature);
else
colorModulation *= light.color;
}
Color curColor = colorModulation;
Vector2 screenPos = new Vector2(2.0f * viewportPos.x - 1.0f, 1.0f - 2.0f * viewportPos.y);
Vector2 radPos = new Vector2(Mathf.Abs(screenPos.x), Mathf.Abs(screenPos.y));
float radius = Mathf.Max(radPos.x, radPos.y); // l1 norm (instead of l2 norm)
float radialsScaleRadius = comp.radialScreenAttenuationCurve.length > 0 ? comp.radialScreenAttenuationCurve.Evaluate(radius) : 1.0f;
float currentIntensity = comp.intensity * element.localIntensity * radialsScaleRadius * distanceAttenuation;
if (currentIntensity <= 0.0f)
continue;
Texture texture = element.lensFlareTexture;
float usedAspectRatio;
if (element.flareType == SRPLensFlareType.Image)
usedAspectRatio = element.preserveAspectRatio ? ((((float)texture.height) / (float)texture.width)) : 1.0f;
else
usedAspectRatio = 1.0f;
float rotation = element.rotation;
Vector2 elemSizeXY;
if (element.preserveAspectRatio)
{
if (usedAspectRatio >= 1.0f)
{
elemSizeXY = new Vector2(element.sizeXY.x / usedAspectRatio, element.sizeXY.y);
}
else
{
elemSizeXY = new Vector2(element.sizeXY.x, element.sizeXY.y * usedAspectRatio);
}
}
else
{
elemSizeXY = new Vector2(element.sizeXY.x, element.sizeXY.y);
}
float scaleSize = 0.1f; // Arbitrary value
Vector2 size = new Vector2(elemSizeXY.x, elemSizeXY.y);
float combinedScale = scaleByDistance * scaleSize * element.uniformScale * comp.scale;
size *= combinedScale;
curColor *= element.tint;
curColor *= currentIntensity;
float angularOffset = SystemInfo.graphicsUVStartsAtTop ? element.angularOffset : -element.angularOffset;
float globalCos0 = Mathf.Cos(-angularOffset * Mathf.Deg2Rad);
float globalSin0 = Mathf.Sin(-angularOffset * Mathf.Deg2Rad);
float position = 2.0f * element.position;
SRPLensFlareBlendMode blendMode = element.blendMode;
int materialPass;
if (blendMode == SRPLensFlareBlendMode.Additive)
materialPass = 0;
else if (blendMode == SRPLensFlareBlendMode.Screen)
materialPass = 1;
else if (blendMode == SRPLensFlareBlendMode.Premultiply)
materialPass = 2;
else if (blendMode == SRPLensFlareBlendMode.Lerp)
materialPass = 3;
else
materialPass = 0;
if (element.flareType == SRPLensFlareType.Image)
{
cmd.DisableShaderKeyword("FLARE_CIRCLE");
cmd.DisableShaderKeyword("FLARE_POLYGON");
}
else if (element.flareType == SRPLensFlareType.Circle)
{
cmd.EnableShaderKeyword("FLARE_CIRCLE");
cmd.DisableShaderKeyword("FLARE_POLYGON");
}
else if (element.flareType == SRPLensFlareType.Polygon)
{
cmd.DisableShaderKeyword("FLARE_CIRCLE");
cmd.EnableShaderKeyword("FLARE_POLYGON");
}
if (element.flareType == SRPLensFlareType.Circle ||
element.flareType == SRPLensFlareType.Polygon)
{
if (element.inverseSDF)
{
cmd.EnableShaderKeyword("FLARE_INVERSE_SDF");
}
else
{
cmd.DisableShaderKeyword("FLARE_INVERSE_SDF");
}
}
else
{
cmd.DisableShaderKeyword("FLARE_INVERSE_SDF");
}
if (element.lensFlareTexture != null)
cmd.SetGlobalTexture(_FlareTex, element.lensFlareTexture);
float usedGradientPosition = Mathf.Clamp01((1.0f - element.edgeOffset) - 1e-6f);
if (element.flareType == SRPLensFlareType.Polygon)
usedGradientPosition = Mathf.Pow(usedGradientPosition + 1.0f, 5);
Vector2 ComputeLocalSize(Vector2 rayOff, Vector2 rayOff0, Vector2 curSize, AnimationCurve distortionCurve)
{
Vector2 rayOffZ = GetLensFlareRayOffset(screenPos, position, globalCos0, globalSin0);
Vector2 localRadPos;
float localRadius;
if (!element.distortionRelativeToCenter)
{
localRadPos = (rayOff - rayOff0) * 0.5f;
localRadius = Mathf.Clamp01(Mathf.Max(Mathf.Abs(localRadPos.x), Mathf.Abs(localRadPos.y))); // l1 norm (instead of l2 norm)
}
else
{
localRadPos = screenPos + (rayOff + new Vector2(element.positionOffset.x, -element.positionOffset.y)) * element.translationScale;
localRadius = Mathf.Clamp01(localRadPos.magnitude); // l2 norm (instead of l1 norm)
}
float localLerpValue = Mathf.Clamp01(distortionCurve.Evaluate(localRadius));
return new Vector2(Mathf.Lerp(curSize.x, element.targetSizeDistortion.x * combinedScale / usedAspectRatio, localLerpValue),
Mathf.Lerp(curSize.y, element.targetSizeDistortion.y * combinedScale, localLerpValue));
}
float usedSDFRoundness = element.sdfRoundness;
cmd.SetGlobalVector(_FlareData3, new Vector4(comp.allowOffScreen ? 1.0f : -1.0f, usedGradientPosition, Mathf.Exp(Mathf.Lerp(0.0f, 4.0f, Mathf.Clamp01(1.0f - element.fallOff))), 1.0f / (float)element.sideCount));
if (element.flareType == SRPLensFlareType.Polygon)
{
float invSide = 1.0f / (float)element.sideCount;
float rCos = Mathf.Cos(Mathf.PI * invSide);
float roundValue = rCos * usedSDFRoundness;
float r = rCos - roundValue;
float an = 2.0f * Mathf.PI * invSide;
float he = r * Mathf.Tan(0.5f * an);
cmd.SetGlobalVector(_FlareData4, new Vector4(usedSDFRoundness, r, an, he));
}
else
{
cmd.SetGlobalVector(_FlareData4, new Vector4(usedSDFRoundness, 0.0f, 0.0f, 0.0f));
}
if (!element.allowMultipleElement || element.count == 1)
{
Vector2 localSize = size;
Vector2 rayOff = GetLensFlareRayOffset(screenPos, position, globalCos0, globalSin0);
if (element.enableRadialDistortion)
{
Vector2 rayOff0 = GetLensFlareRayOffset(screenPos, 0.0f, globalCos0, globalSin0);
localSize = ComputeLocalSize(rayOff, rayOff0, localSize, element.distortionCurve);
}
Vector4 flareData0 = GetFlareData0(screenPos, element.translationScale, rayOff, vScreenRatio, rotation, position, angularOffset, element.positionOffset, element.autoRotate);
cmd.SetGlobalVector(_FlareData0, flareData0);
cmd.SetGlobalVector(_FlareData2, new Vector4(screenPos.x, screenPos.y, localSize.x, localSize.y));
cmd.SetGlobalVector(_FlareColorValue, curColor);
UnityEngine.Rendering.Blitter.DrawQuad(cmd, lensFlareShader, materialPass);
}
else
{
float dLength = 2.0f * element.lengthSpread / ((float)(element.count - 1));
if (element.distribution == SRPLensFlareDistribution.Uniform)
{
float uniformAngle = 0.0f;
for (int elemIdx = 0; elemIdx < element.count; ++elemIdx)
{
Vector2 localSize = size;
Vector2 rayOff = GetLensFlareRayOffset(screenPos, position, globalCos0, globalSin0);
if (element.enableRadialDistortion)
{
Vector2 rayOff0 = GetLensFlareRayOffset(screenPos, 0.0f, globalCos0, globalSin0);
localSize = ComputeLocalSize(rayOff, rayOff0, localSize, element.distortionCurve);
}
float timeScale = element.count >= 2 ? ((float)elemIdx) / ((float)(element.count - 1)) : 0.5f;
Color col = element.colorGradient.Evaluate(timeScale);
Vector4 flareData0 = GetFlareData0(screenPos, element.translationScale, rayOff, vScreenRatio, rotation + uniformAngle, position, angularOffset, element.positionOffset, element.autoRotate);
cmd.SetGlobalVector(_FlareData0, flareData0);
cmd.SetGlobalVector(_FlareData2, new Vector4(screenPos.x, screenPos.y, localSize.x, localSize.y));
cmd.SetGlobalVector(_FlareColorValue, curColor * col);
UnityEngine.Rendering.Blitter.DrawQuad(cmd, lensFlareShader, materialPass);
position += dLength;
uniformAngle += element.uniformAngle;
}
}
else if (element.distribution == SRPLensFlareDistribution.Random)
{
Random.State backupRandState = UnityEngine.Random.state;
Random.InitState(element.seed);
Vector2 side = new Vector2(globalSin0, globalCos0);
side *= element.positionVariation.y;
float RandomRange(float min, float max)
{
return Random.Range(min, max);
}
for (int elemIdx = 0; elemIdx < element.count; ++elemIdx)
{
float localIntensity = RandomRange(-1.0f, 1.0f) * element.intensityVariation + 1.0f;
Vector2 rayOff = GetLensFlareRayOffset(screenPos, position, globalCos0, globalSin0);
Vector2 localSize = size;
if (element.enableRadialDistortion)
{
Vector2 rayOff0 = GetLensFlareRayOffset(screenPos, 0.0f, globalCos0, globalSin0);
localSize = ComputeLocalSize(rayOff, rayOff0, localSize, element.distortionCurve);
}
localSize += localSize * (element.scaleVariation * RandomRange(-1.0f, 1.0f));
Color randCol = element.colorGradient.Evaluate(RandomRange(0.0f, 1.0f));
Vector2 localPositionOffset = element.positionOffset + RandomRange(-1.0f, 1.0f) * side;
float localRotation = rotation + RandomRange(-Mathf.PI, Mathf.PI) * element.rotationVariation;
if (localIntensity > 0.0f)
{
Vector4 flareData0 = GetFlareData0(screenPos, element.translationScale, rayOff, vScreenRatio, localRotation, position, angularOffset, localPositionOffset, element.autoRotate);
cmd.SetGlobalVector(_FlareData0, flareData0);
cmd.SetGlobalVector(_FlareData2, new Vector4(screenPos.x, screenPos.y, localSize.x, localSize.y));
cmd.SetGlobalVector(_FlareColorValue, curColor * randCol * localIntensity);
UnityEngine.Rendering.Blitter.DrawQuad(cmd, lensFlareShader, materialPass);
}
position += dLength;
position += 0.5f * dLength * RandomRange(-1.0f, 1.0f) * element.positionVariation.x;
}
Random.state = backupRandState;
}
else if (element.distribution == SRPLensFlareDistribution.Curve)
{
for (int elemIdx = 0; elemIdx < element.count; ++elemIdx)
{
float timeScale = element.count >= 2 ? ((float)elemIdx) / ((float)(element.count - 1)) : 0.5f;
Color col = element.colorGradient.Evaluate(timeScale);
float positionSpacing = element.positionCurve.length > 0 ? element.positionCurve.Evaluate(timeScale) : 1.0f;
float localPos = position + 2.0f * element.lengthSpread * positionSpacing;
Vector2 rayOff = GetLensFlareRayOffset(screenPos, localPos, globalCos0, globalSin0);
Vector2 localSize = size;
if (element.enableRadialDistortion)
{
Vector2 rayOff0 = GetLensFlareRayOffset(screenPos, 0.0f, globalCos0, globalSin0);
localSize = ComputeLocalSize(rayOff, rayOff0, localSize, element.distortionCurve);
}
float sizeCurveValue = element.scaleCurve.length > 0 ? element.scaleCurve.Evaluate(timeScale) : 1.0f;
localSize *= sizeCurveValue;
float angleFromCurve = element.uniformAngleCurve.Evaluate(timeScale) * (180.0f - (180.0f / (float)element.count));
Vector4 flareData0 = GetFlareData0(screenPos, element.translationScale, rayOff, vScreenRatio, rotation + angleFromCurve, localPos, angularOffset, element.positionOffset, element.autoRotate);
cmd.SetGlobalVector(_FlareData0, flareData0);
cmd.SetGlobalVector(_FlareData2, new Vector4(screenPos.x, screenPos.y, localSize.x, localSize.y));
cmd.SetGlobalVector(_FlareColorValue, curColor * col);
UnityEngine.Rendering.Blitter.DrawQuad(cmd, lensFlareShader, materialPass);
}
}
}
}
}
}
/// <summary>
/// Remove a lens flare data which exist in the pool.
/// </summary>
/// <param name="data">The data which exist in the pool</param>
public void RemoveData(LensFlareComponentSRP data)
{
Debug.Assert(Instance == this, "LensFlareCommonSRP can have only one instance");
if (m_Data.Contains(data))
{
m_Data.Remove(data);
}
}
#region Panini Projection
static Vector2 DoPaniniProjection(Vector2 screenPos, float actualWidth, float actualHeight, float fieldOfView, float paniniProjectionCropToFit, float paniniProjectionDistance)
{
Vector2 viewExtents = CalcViewExtents(actualWidth, actualHeight, fieldOfView);
Vector2 cropExtents = CalcCropExtents(actualWidth, actualHeight, fieldOfView, paniniProjectionDistance);
float scaleX = cropExtents.x / viewExtents.x;
float scaleY = cropExtents.y / viewExtents.y;
float scaleF = Mathf.Min(scaleX, scaleY);
float paniniD = paniniProjectionDistance;
float paniniS = Mathf.Lerp(1.0f, Mathf.Clamp01(scaleF), paniniProjectionCropToFit);
Vector2 pos = new Vector2(2.0f * screenPos.x - 1.0f, 2.0f * screenPos.y - 1.0f);
Vector2 projPos = Panini_Generic_Inv(pos * viewExtents, paniniD) / (viewExtents * paniniS);
return new Vector2(0.5f * projPos.x + 0.5f, 0.5f * projPos.y + 0.5f);
}
static Vector2 CalcViewExtents(float actualWidth, float actualHeight, float fieldOfView)
{
float fovY = fieldOfView * Mathf.Deg2Rad;
float aspect = actualWidth / actualHeight;
float viewExtY = Mathf.Tan(0.5f * fovY);
float viewExtX = aspect * viewExtY;
return new Vector2(viewExtX, viewExtY);
}
static Vector2 CalcCropExtents(float actualWidth, float actualHeight, float fieldOfView, float d)
{
// given
// S----------- E--X-------
// | ` ~. /,´
// |-- --- Q
// | ,/ `
// 1 | ,´/ `
// | ,´ / ´
// | ,´ / ´
// |,` / ,
// O /
// | / ,
// d | /
// | / ,
// |/ .
// P
// | ´
// | , ´
// +- ´
//
// have X
// want to find E
float viewDist = 1.0f + d;
Vector2 projPos = CalcViewExtents(actualWidth, actualHeight, fieldOfView);
float projHyp = Mathf.Sqrt(projPos.x * projPos.x + 1.0f);
float cylDistMinusD = 1.0f / projHyp;
float cylDist = cylDistMinusD + d;
Vector2 cylPos = projPos * cylDistMinusD;
return cylPos * (viewDist / cylDist);
}
static Vector2 Panini_Generic_Inv(Vector2 projPos, float d)
{
// given
// S----------- E--X-------
// | ` ~. /,´
// |-- --- Q
// | ,/ `
// 1 | ,´/ `
// | ,´ / ´
// | ,´ / ´
// |,` / ,
// O /
// | / ,
// d | /
// | / ,
// |/ .
// P
// | ´
// | , ´
// +- ´
//
// have X
// want to find E
float viewDist = 1.0f + d;
float projHyp = Mathf.Sqrt(projPos.x * projPos.x + 1.0f);
float cylDistMinusD = 1.0f / projHyp;
float cylDist = cylDistMinusD + d;
Vector2 cylPos = projPos * cylDistMinusD;
return cylPos * (viewDist / cylDist);
}
#endregion
}
}