You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
627 lines
22 KiB
627 lines
22 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using UnityEngine.Rendering.RenderGraphModule;
|
|
using UnityEngine.Serialization;
|
|
|
|
namespace UnityEngine.Rendering.HighDefinition
|
|
{
|
|
/// <summary>
|
|
/// Component containing additional mesh rendering features for HDRP.
|
|
/// </summary>
|
|
[AddComponentMenu("Mesh/Mesh Renderer Extension")]
|
|
[RequireComponent(typeof(MeshRenderer))]
|
|
[RequireComponent(typeof(MeshFilter))]
|
|
[ExecuteAlways]
|
|
public class HDAdditionalMeshRendererSettings : MonoBehaviour, ILineRenderer
|
|
{
|
|
private MeshRenderer m_MeshRenderer;
|
|
private MeshFilter m_MeshFilter;
|
|
|
|
// This is guaranteed to be the identifier since it is what the sub target will emit.
|
|
private const string kVertexSetupComputeAssetIdentifier = "VertexSetup";
|
|
private const string kOffscreenShadingPassIdentifier = "LineRenderingOffscreenShading";
|
|
|
|
[SerializeField]
|
|
private bool m_EnableHighQualityLineRendering;
|
|
|
|
/// <summary>
|
|
/// Set the enablement of high quality line rendering for this mesh renderer.
|
|
/// </summary>
|
|
public bool enableHighQualityLineRendering
|
|
{
|
|
get
|
|
{
|
|
return m_EnableHighQualityLineRendering;
|
|
}
|
|
set
|
|
{
|
|
m_EnableHighQualityLineRendering = value;
|
|
OnValidate();
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private LineRendering.RendererGroup m_RendererGroup = LineRendering.RendererGroup.None;
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering merge group for this mesh renderer.
|
|
/// </summary>
|
|
public LineRendering.RendererGroup rendererGroup
|
|
{
|
|
get
|
|
{
|
|
return m_RendererGroup;
|
|
}
|
|
set
|
|
{
|
|
m_RendererGroup = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private LineRendering.RendererLODMode m_RendererLODMode = LineRendering.RendererLODMode.None;
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering level of detail mode for this mesh renderer.
|
|
/// </summary>
|
|
public LineRendering.RendererLODMode rendererLODMode
|
|
{
|
|
get
|
|
{
|
|
return m_RendererLODMode;
|
|
}
|
|
set
|
|
{
|
|
m_RendererLODMode = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField, Range(0f, 1f)]
|
|
private float m_RendererLODFixed = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering fixed level of detail for this mesh renderer.
|
|
/// </summary>
|
|
public float rendererLODFixed
|
|
{
|
|
get
|
|
{
|
|
return m_RendererLODFixed;
|
|
}
|
|
set
|
|
{
|
|
m_RendererLODFixed = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private AnimationCurve m_RendererLODCameraDistanceCurve = AnimationCurve.EaseInOut(1f, 1f, 10f, 0.05f);
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering level of detail curve for this mesh renderer.
|
|
/// </summary>
|
|
public AnimationCurve rendererLODCameraDistanceCurve
|
|
{
|
|
get
|
|
{
|
|
return m_RendererLODCameraDistanceCurve;
|
|
}
|
|
set
|
|
{
|
|
m_RendererLODCameraDistanceCurve = value;
|
|
}
|
|
}
|
|
|
|
[FormerlySerializedAs("m_RendererLODCameraCoverageCurve")] [SerializeField]
|
|
private AnimationCurve m_RendererLODScreenCoverageCurve = AnimationCurve.EaseInOut(0f, 0.01f, 1f, 1.0f);
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering level of detail curve for this mesh renderer.
|
|
/// </summary>
|
|
public AnimationCurve rendererLODScreenCoverageCurve
|
|
{
|
|
get
|
|
{
|
|
return m_RendererLODScreenCoverageCurve;
|
|
}
|
|
set
|
|
{
|
|
m_RendererLODScreenCoverageCurve = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField, Range(0.001f, 1f)]
|
|
private float m_ShadingSampleFraction = 1.0f;
|
|
|
|
/// <summary>
|
|
/// Sets the high quality line rendering shading rate for this mesh renderer.
|
|
/// </summary>
|
|
public float shadingSampleFraction
|
|
{
|
|
get
|
|
{
|
|
return m_ShadingSampleFraction;
|
|
}
|
|
set
|
|
{
|
|
m_ShadingSampleFraction = value;
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
private uint m_IndexCount;
|
|
|
|
[SerializeField]
|
|
private uint m_SegmentsPerLine;
|
|
|
|
[SerializeField]
|
|
private uint m_LineCount;
|
|
|
|
[SerializeField]
|
|
private ComputeShader m_VertexSetupCompute;
|
|
|
|
[NonSerialized]
|
|
internal Matrix4x4 m_PreviousLocalToWorldMatrix = Matrix4x4.identity;
|
|
|
|
[NonSerialized]
|
|
private Material m_OldMaterial = null;
|
|
|
|
[NonSerialized]
|
|
private GraphicsBuffer m_LODBuffer;
|
|
|
|
[NonSerialized] private GraphicsBuffer m_IndexBuffer;
|
|
|
|
private bool TryGetDependentComponents() => TryGetComponent(out m_MeshRenderer) && TryGetComponent(out m_MeshFilter);
|
|
private bool ComponentsValid() => m_MeshRenderer != null && m_MeshFilter != null;
|
|
|
|
private void ForceSetBaseMeshRendererPasses(bool enabled)
|
|
{
|
|
if (!ComponentsValid())
|
|
return;
|
|
|
|
if (m_MeshRenderer.sharedMaterial == null)
|
|
return;
|
|
|
|
// TODO: Currently the material inspector overrides this in the editor...
|
|
|
|
// Use render queue mechanism to disable the normal depth/motion/color passes.
|
|
// This is a more reliable way than Material.SetShaderPassEnabled which seems to not work for motion vectors.
|
|
// We do it this way instead of overriding MeshRenderer.shadowCastingMode to ShadowsOnly for less confusing UI.
|
|
m_MeshRenderer.sharedMaterial.renderQueue = !enabled ? (int)HDRenderQueue.Priority.LineRendering : -1;
|
|
}
|
|
|
|
private static bool CameraSupportsLineRendering(Camera camera)
|
|
{
|
|
return HDRenderPipeline.LineRenderingIsEnabled(HDCamera.GetOrCreate(camera), out var unused1);
|
|
}
|
|
|
|
private void SetLineRenderingEnabled(bool enabled)
|
|
{
|
|
if (enabled)
|
|
{
|
|
LineRendering.AddRenderer(this);
|
|
ForceSetBaseMeshRendererPasses(false);
|
|
}
|
|
else
|
|
{
|
|
LineRendering.RemoveRenderer(this);
|
|
ForceSetBaseMeshRendererPasses(true);
|
|
}
|
|
}
|
|
|
|
private void CallbackUpdatePreviousLocalToWorldMatrix(ScriptableRenderContext unused0, List<Camera> unused1) => m_PreviousLocalToWorldMatrix = transform.localToWorldMatrix;
|
|
|
|
void CallbackForceRendererDisable(ScriptableRenderContext unused0, Camera camera)
|
|
{
|
|
if (!m_EnableHighQualityLineRendering)
|
|
return;
|
|
|
|
if (!CameraSupportsLineRendering(camera))
|
|
{
|
|
ForceSetBaseMeshRendererPasses(true);
|
|
}
|
|
}
|
|
|
|
void CallbackForceRendererEnable(ScriptableRenderContext unused0, Camera camera)
|
|
{
|
|
if (!m_EnableHighQualityLineRendering)
|
|
return;
|
|
|
|
if (!CameraSupportsLineRendering(camera))
|
|
{
|
|
ForceSetBaseMeshRendererPasses(false);
|
|
}
|
|
}
|
|
|
|
private void RegisterCallbacks()
|
|
{
|
|
RenderPipelineManager.beginCameraRendering += CallbackForceRendererDisable;
|
|
RenderPipelineManager.endCameraRendering += CallbackForceRendererEnable;
|
|
RenderPipelineManager.endContextRendering += CallbackUpdatePreviousLocalToWorldMatrix;
|
|
}
|
|
|
|
private void DeRegisterCallbacks()
|
|
{
|
|
RenderPipelineManager.beginCameraRendering -= CallbackForceRendererDisable;
|
|
RenderPipelineManager.endCameraRendering -= CallbackForceRendererEnable;
|
|
RenderPipelineManager.endContextRendering -= CallbackUpdatePreviousLocalToWorldMatrix;
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
#if UNITY_EDITOR
|
|
// Need to do this here for similar reasons as we do in OnValidate.
|
|
s_FirstOnValidateCalled = false;
|
|
#endif
|
|
|
|
RegisterCallbacks();
|
|
TryGetDependentComponents();
|
|
|
|
if (m_EnableHighQualityLineRendering)
|
|
{
|
|
SetLineRenderingEnabled(true);
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
DeRegisterCallbacks();
|
|
|
|
if (m_EnableHighQualityLineRendering)
|
|
{
|
|
CoreUtils.SafeRelease(m_LODBuffer);
|
|
m_LODBuffer = null;
|
|
|
|
CoreUtils.SafeRelease(m_IndexBuffer);
|
|
m_IndexBuffer = null;
|
|
|
|
SetLineRenderingEnabled(false);
|
|
}
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
DeRegisterCallbacks();
|
|
|
|
if (m_EnableHighQualityLineRendering)
|
|
{
|
|
CoreUtils.SafeRelease(m_LODBuffer);
|
|
m_LODBuffer = null;
|
|
|
|
CoreUtils.SafeRelease(m_IndexBuffer);
|
|
m_IndexBuffer = null;
|
|
|
|
SetLineRenderingEnabled(false);
|
|
}
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (m_EnableHighQualityLineRendering)
|
|
{
|
|
if (LineRendererIsValid())
|
|
{
|
|
ForceSetBaseMeshRendererPasses(false);
|
|
}
|
|
else
|
|
{
|
|
ForceSetBaseMeshRendererPasses(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// We need this editor workaround to skip nullifying the compute asset during the import OnValidate,
|
|
// because the AssetDatabase is not set up yet and IsLineRenderingMaterial will always fail.
|
|
[NonSerialized]
|
|
private bool s_FirstOnValidateCalled = false;
|
|
#endif
|
|
|
|
internal void OnValidate()
|
|
{
|
|
if (!ComponentsValid())
|
|
return;
|
|
|
|
if (!m_EnableHighQualityLineRendering)
|
|
{
|
|
SetLineRenderingEnabled(false);
|
|
return;
|
|
}
|
|
|
|
SetLineRenderingEnabled(true);
|
|
|
|
var material = m_MeshRenderer.sharedMaterial;
|
|
|
|
if (m_OldMaterial != material)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (material != null)
|
|
{
|
|
if (s_FirstOnValidateCalled && !IsLineRenderingMaterial(material))
|
|
{
|
|
m_VertexSetupCompute = null;
|
|
}
|
|
else if (material.shader != null)
|
|
{
|
|
var shaderPath = AssetDatabase.GetAssetPath(material.shader);
|
|
var shaderAssets = AssetDatabase.LoadAllAssetsAtPath(shaderPath);
|
|
|
|
foreach (var asset in shaderAssets)
|
|
{
|
|
if (asset is ComputeShader shader)
|
|
{
|
|
if (shader.name.Contains(kVertexSetupComputeAssetIdentifier))
|
|
m_VertexSetupCompute = shader;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!s_FirstOnValidateCalled)
|
|
s_FirstOnValidateCalled = true;
|
|
#else
|
|
if (!GraphicsSettings.TryGetRenderPipelineSettings<HDRenderPipelineRuntimeAssets>(out var assets))
|
|
{
|
|
Debug.LogError("Failed to load runtime resources.");
|
|
return;
|
|
}
|
|
|
|
if (!assets.computeMaterialLibrary)
|
|
{
|
|
Debug.LogError("Failed to load compute material library.");
|
|
return;
|
|
}
|
|
|
|
if (!assets.computeMaterialLibrary.Get(material.shader, out m_VertexSetupCompute))
|
|
{
|
|
Debug.LogError("Failed to load compute material for the given shader.");
|
|
return;
|
|
}
|
|
#endif
|
|
m_OldMaterial = material;
|
|
}
|
|
}
|
|
|
|
internal static bool IsLineRenderingMaterial(Material mat)
|
|
{
|
|
#if UNITY_EDITOR
|
|
// Try to get the compute shader.
|
|
if (mat != null && mat.shader != null)
|
|
{
|
|
var shaderPath = AssetDatabase.GetAssetPath(mat.shader);
|
|
var shaderAssets = AssetDatabase.LoadAllAssetsAtPath(shaderPath);
|
|
|
|
var materialHasRequiredComputeAssets = true;
|
|
{
|
|
materialHasRequiredComputeAssets &= shaderAssets.Any(o => o.name.Contains(kVertexSetupComputeAssetIdentifier));
|
|
}
|
|
return materialHasRequiredComputeAssets && mat.FindPass(kOffscreenShadingPassIdentifier) != -1;
|
|
}
|
|
|
|
return false;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
internal SphericalHarmonicsL2 GetLightProbe()
|
|
{
|
|
if (m_MeshRenderer.lightProbeUsage == LightProbeUsage.CustomProvided)
|
|
return new SphericalHarmonicsL2(); // TODO
|
|
|
|
SphericalHarmonicsL2 sh;
|
|
{
|
|
if (m_MeshRenderer.lightProbeUsage == LightProbeUsage.BlendProbes)
|
|
{
|
|
// Use the supporting renderer to fetch the light probe.
|
|
// (Can be null renderer too, but provided one speeds up the search).
|
|
var position = m_MeshRenderer.probeAnchor ? m_MeshRenderer.probeAnchor.position : transform.position;
|
|
LightProbes.GetInterpolatedProbe(position, m_MeshRenderer, out sh);
|
|
}
|
|
else
|
|
{
|
|
sh = new SphericalHarmonicsL2();
|
|
sh.AddAmbientLight(Color.black);
|
|
}
|
|
}
|
|
return sh;
|
|
}
|
|
|
|
internal void ComputeLODDataIfNeeded()
|
|
{
|
|
var mesh = m_MeshFilter.sharedMesh;
|
|
|
|
if (mesh == null)
|
|
return;
|
|
|
|
var currentIndexCount = mesh.GetIndexCount(0);
|
|
|
|
// TODO: More than one sub-mesh.
|
|
if (m_IndexCount == currentIndexCount && m_LODBuffer != null)
|
|
return;
|
|
|
|
// Compute # segments-per-line.
|
|
m_SegmentsPerLine = 0;
|
|
{
|
|
var meshIndices = mesh.GetIndices(0);
|
|
|
|
for (uint i = 0; i < meshIndices.Length; i += 2u)
|
|
{
|
|
if (m_SegmentsPerLine != meshIndices[i])
|
|
break;
|
|
|
|
m_SegmentsPerLine++;
|
|
}
|
|
}
|
|
|
|
// Compute # lines.
|
|
m_LineCount = (currentIndexCount / 2) / m_SegmentsPerLine;
|
|
|
|
// Create the LOD buffer.
|
|
{
|
|
CoreUtils.SafeRelease(m_LODBuffer);
|
|
|
|
// TODO: Make this faster.
|
|
|
|
var nums = Enumerable.Range(0, (int)m_LineCount).ToArray();
|
|
|
|
// Hard-coded seed for stable CI.
|
|
var rnd = new System.Random(1337);
|
|
|
|
for (int i = 0;i < nums.Length;++i)
|
|
{
|
|
int randomIndex = rnd.Next(nums.Length);
|
|
(nums[randomIndex], nums[i]) = (nums[i], nums[randomIndex]);
|
|
}
|
|
|
|
m_LODBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Raw, GraphicsBuffer.UsageFlags.None, (int)m_LineCount, sizeof(uint));
|
|
m_LODBuffer.SetData(nums);
|
|
}
|
|
|
|
m_IndexCount = currentIndexCount;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if this mesh renderer is valid for high quality line rendering.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool LineRendererIsValid()
|
|
{
|
|
if (!ComponentsValid())
|
|
return false;
|
|
|
|
var mesh = m_MeshFilter.sharedMesh;
|
|
var material = m_MeshRenderer.sharedMaterial;
|
|
|
|
if (material == null || mesh == null || m_VertexSetupCompute == null)
|
|
return false;
|
|
|
|
if (m_MeshRenderer.shadowCastingMode == ShadowCastingMode.ShadowsOnly)
|
|
return false;
|
|
|
|
return mesh.GetTopology(0) == MeshTopology.Lines && material.FindPass(kOffscreenShadingPassIdentifier) != -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts the required rendering data for line rendering.
|
|
/// </summary>
|
|
/// <param name="renderGraph">Render Graph</param>
|
|
/// <param name="camera">The camera for which the line rendering is being gathered.</param>
|
|
/// <returns></returns>
|
|
public LineRendering.RendererData GetLineRendererData(RenderGraph renderGraph, Camera camera)
|
|
{
|
|
CoreUtils.SafeRelease(m_IndexBuffer);
|
|
|
|
// Required for binding internal buffer resources of the mesh in the rasterization.
|
|
m_MeshFilter.sharedMesh.indexBufferTarget |= GraphicsBuffer.Target.Raw;
|
|
m_IndexBuffer = m_MeshFilter.sharedMesh.GetIndexBuffer();
|
|
|
|
// Re-compute various data for LOD if there was a topological change to the mesh.
|
|
ComputeLODDataIfNeeded();
|
|
|
|
// Override
|
|
var strandLOD = m_RendererLODFixed;
|
|
|
|
if (m_RendererLODMode == LineRendering.RendererLODMode.CameraDistance && m_RendererLODCameraDistanceCurve != null)
|
|
{
|
|
var distanceToCamera = Vector3.Distance(camera.transform.position, m_MeshRenderer.bounds.center);
|
|
strandLOD = m_RendererLODCameraDistanceCurve.Evaluate(distanceToCamera);
|
|
}
|
|
else if (m_RendererLODMode == LineRendering.RendererLODMode.ScreenCoverage)
|
|
{
|
|
Vector3 min = m_MeshRenderer.bounds.min;
|
|
Vector3 max = m_MeshRenderer.bounds.max;
|
|
|
|
var boundsCorners = new Vector3[]
|
|
{
|
|
new (min.x, min.y, min.z),
|
|
new (max.x, min.y, min.z),
|
|
new (min.x, max.y, min.z),
|
|
new (max.x, max.y, min.z),
|
|
new (min.x, min.y, max.z),
|
|
new (max.x, min.y, max.z),
|
|
new (min.x, max.y, max.z),
|
|
new (max.x, max.y, max.z),
|
|
};
|
|
|
|
Vector2 ComputeScreenSpacePoint(Vector3 p)
|
|
{
|
|
p = camera.WorldToScreenPoint(p);
|
|
return new Vector2(p.x, p.y);
|
|
}
|
|
|
|
var minP = Vector2.positiveInfinity;
|
|
var maxP = Vector2.negativeInfinity;
|
|
|
|
foreach (var corner in boundsCorners)
|
|
{
|
|
var p = ComputeScreenSpacePoint(corner);
|
|
|
|
minP = Vector2.Min(p, minP);
|
|
maxP = Vector2.Max(p, maxP);
|
|
}
|
|
|
|
// Determine the area of the bounds in screen space.
|
|
var length = maxP.x - minP.x;
|
|
var width = maxP.y - minP.y;
|
|
var area = length * width;
|
|
|
|
// Screen coverage is the ratio between projected bounds and viewport size.
|
|
var screenCoverage = area / (Screen.width * Screen.height);
|
|
|
|
// Need to implement our own smoothstep since Mathf.SmoothStep is not the same
|
|
// behaviour as the HLSL intrinsic.
|
|
float Smoothstep(float edge0, float edge1, float x)
|
|
{
|
|
// Scale, bias and saturate x to 0..1 range
|
|
x = Mathf.Clamp01((x - edge0) / (edge1 - edge0));
|
|
|
|
// Evaluate polynomial
|
|
return x * x * (3 - 2 * x);
|
|
}
|
|
|
|
if (m_RendererLODScreenCoverageCurve != null)
|
|
{
|
|
strandLOD = m_RendererLODScreenCoverageCurve.Evaluate(screenCoverage);
|
|
}
|
|
else
|
|
{
|
|
// Remap the coverage s.t. we control when the min and max lod occur.
|
|
screenCoverage = Smoothstep(0.01f, 0.1f, screenCoverage);
|
|
// Minimum screen coverage LOD is 1% of strands.
|
|
strandLOD = Mathf.Lerp(0.1f, 1.0f, screenCoverage);
|
|
}
|
|
|
|
}
|
|
|
|
return new LineRendering.RendererData
|
|
{
|
|
probe = GetLightProbe(),
|
|
mesh = m_MeshFilter.sharedMesh,
|
|
matrixW = transform.localToWorldMatrix,
|
|
group = m_RendererGroup,
|
|
matrixWP = m_PreviousLocalToWorldMatrix,
|
|
material = m_MeshRenderer.sharedMaterial,
|
|
motionVectorParams = new Vector4(0, m_MeshRenderer.motionVectorGenerationMode == MotionVectorGenerationMode.ForceNoMotion ? 0 : 1, 0, m_MeshRenderer.motionVectorGenerationMode == MotionVectorGenerationMode.Camera ? 1 : 0),
|
|
vertexSetupCompute = m_VertexSetupCompute,
|
|
offscreenShadingPass = m_MeshRenderer.sharedMaterial.FindPass(HDShaderPassNames.s_LineRenderingOffscreenShading),
|
|
renderingLayerMask = m_MeshRenderer.renderingLayerMask,
|
|
indexBuffer = renderGraph.ImportBuffer(m_IndexBuffer),
|
|
distanceToCamera = Vector3.Distance(transform.position, camera.transform.position),
|
|
bounds = m_MeshRenderer.bounds,
|
|
|
|
// LOD
|
|
segmentsPerLine = (int)m_SegmentsPerLine,
|
|
lineCount = (int)m_LineCount,
|
|
lodBuffer = renderGraph.ImportBuffer(m_LODBuffer),
|
|
lodMode = m_RendererLODMode,
|
|
lod = strandLOD,
|
|
shadingFraction = m_ShadingSampleFraction,
|
|
hash = GetInstanceID()
|
|
};
|
|
}
|
|
}
|
|
}
|