1609 lines
50 KiB
C#
1609 lines
50 KiB
C#
/// <summary>
|
|
/// Highlight Plus - (c) 2018-2019 Ramiro Oliva (Kronnect)
|
|
/// </summary>
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Rendering;
|
|
|
|
namespace HighlightPlus
|
|
{
|
|
|
|
public delegate bool OnObjectHighlightStartEvent (GameObject obj);
|
|
public delegate void OnObjectHighlightEndEvent (GameObject obj);
|
|
public delegate bool OnRendererHighlightEvent (Renderer renderer);
|
|
|
|
|
|
public enum SeeThroughMode
|
|
{
|
|
WhenHighlighted = 0,
|
|
AlwaysWhenOccluded = 1,
|
|
Never = 2
|
|
}
|
|
|
|
public enum QualityLevel
|
|
{
|
|
Fastest = 0,
|
|
High = 1,
|
|
Highest = 2
|
|
}
|
|
|
|
public enum TargetOptions
|
|
{
|
|
Children,
|
|
OnlyThisObject,
|
|
RootToChildren,
|
|
Layer
|
|
}
|
|
|
|
[Serializable]
|
|
public struct GlowPassData
|
|
{
|
|
public float offset;
|
|
public float alpha;
|
|
public Color color;
|
|
}
|
|
|
|
[ExecuteInEditMode]
|
|
[HelpURL ("https://kronnect.freshdesk.com/support/solutions/42000065090")]
|
|
public partial class HighlightEffect : MonoBehaviour
|
|
{
|
|
|
|
/// <summary>
|
|
/// Gets or sets the current profile. To load a profile and apply its settings at runtime, please use ProfileLoad() method.
|
|
/// </summary>
|
|
public HighlightProfile profile;
|
|
|
|
/// <summary>
|
|
/// Sets if changes to the original profile should propagate to this effect.
|
|
/// </summary>
|
|
[Tooltip ("If enabled, settings will be synced with profile.")]
|
|
public bool profileSync;
|
|
|
|
/// <summary>
|
|
/// Makes the effects visible in the SceneView.
|
|
/// </summary>
|
|
public bool previewInEditor = true;
|
|
|
|
/// <summary>
|
|
/// Specifies which objects are affected by this effect.
|
|
/// </summary>
|
|
public TargetOptions effectGroup = TargetOptions.Children;
|
|
|
|
/// <summary>
|
|
/// The layer that contains the affected objects by this effect when effectGroup is set to LayerMask.
|
|
/// </summary>
|
|
public LayerMask effectGroupLayer = -1;
|
|
|
|
/// <summary>
|
|
/// The alpha threshold for transparent cutout objects. Pixels with alpha below this value will be discarded.
|
|
/// </summary>
|
|
[Range (0, 1)]
|
|
public float alphaCutOff = 0;
|
|
|
|
/// <summary>
|
|
/// If back facing triangles are ignored. Backfaces triangles are not visible but you may set this property to false to force highlight effects to act on those triangles as well.
|
|
/// </summary>
|
|
public bool cullBackFaces = true;
|
|
|
|
/// <summary>
|
|
/// Show highlight effects even if the object is currently not visible. This option is useful if the affected objects are rendered using GPU instancing tools which render directly to the GPU without creating real game object geometry in CPU.
|
|
/// </summary>
|
|
[Tooltip ("Show highlight effects even if the object is not visible. If this object or its children use GPU Instancing tools, the MeshRenderer can be disabled although the object is visible. In this case, this option is useful to enable highlighting.")]
|
|
public bool ignoreObjectVisibility;
|
|
|
|
/// <summary>
|
|
/// Enable to support reflection probes
|
|
/// </summary>
|
|
[Tooltip ("Support reflection probes. Enable only if you want the effects to be visible in reflections.")]
|
|
public bool reflectionProbes;
|
|
|
|
|
|
/// <summary>
|
|
/// Ignores highlight effects on this object.
|
|
/// </summary>
|
|
[Tooltip ("Ignore highlighting on this object.")]
|
|
public bool ignore;
|
|
|
|
[SerializeField]
|
|
bool _highlighted;
|
|
|
|
public bool highlighted { get { return _highlighted; } set { SetHighlighted (value); } }
|
|
|
|
public float fadeInDuration;
|
|
public float fadeOutDuration;
|
|
|
|
#if UNITY_2019_OR_NEWER
|
|
public bool flipY = true;
|
|
|
|
#else
|
|
public bool flipY;
|
|
#endif
|
|
|
|
public bool constantWidth = true;
|
|
|
|
[Range (0, 1)]
|
|
public float overlay = 0.5f;
|
|
public Color overlayColor = Color.yellow;
|
|
public float overlayAnimationSpeed = 1f;
|
|
[Range (0, 1)]
|
|
public float overlayMinIntensity = 0.5f;
|
|
[Range (0, 1)]
|
|
public float overlayBlending = 1.0f;
|
|
|
|
[Range (0, 1)]
|
|
public float outline = 1f;
|
|
public Color outlineColor = Color.black;
|
|
public float outlineWidth = 0.45f;
|
|
public QualityLevel outlineQuality = QualityLevel.Highest;
|
|
[Range (1, 8)]
|
|
public int outlineDownsampling = 2;
|
|
public bool outlineAlwaysOnTop;
|
|
public bool outlineOptimalBlit = true;
|
|
public bool outlineBlitDebug;
|
|
|
|
[Range (0, 5)]
|
|
public float glow = 1f;
|
|
public float glowWidth = 0.4f;
|
|
public QualityLevel glowQuality = QualityLevel.Highest;
|
|
[Range (1, 8)]
|
|
public int glowDownsampling = 2;
|
|
public Color glowHQColor = new Color (0.64f, 1f, 0f, 1f);
|
|
public bool glowDithering = true;
|
|
public float glowMagicNumber1 = 0.75f;
|
|
public float glowMagicNumber2 = 0.5f;
|
|
public float glowAnimationSpeed = 1f;
|
|
public bool glowAlwaysOnTop;
|
|
public bool glowOptimalBlit = true;
|
|
public bool glowBlitDebug;
|
|
public GlowPassData[] glowPasses;
|
|
|
|
[Range (0, 5f)]
|
|
public float innerGlow = 0f;
|
|
[Range (0, 2)]
|
|
public float innerGlowWidth = 1f;
|
|
public Color innerGlowColor = Color.white;
|
|
public bool innerGlowAlwaysOnTop;
|
|
|
|
public bool targetFX;
|
|
public Texture2D targetFXTexture;
|
|
public Color targetFXColor = Color.white;
|
|
public Transform targetFXCenter;
|
|
public float targetFXRotationSpeed = 50f;
|
|
public float targetFXInitialScale = 4f;
|
|
public float targetFXEndScale = 1.5f;
|
|
public float targetFXTransitionDuration = 0.5f;
|
|
public float targetFXStayDuration = 1.5f;
|
|
|
|
public event OnObjectHighlightStartEvent OnObjectHighlightStart;
|
|
public event OnObjectHighlightEndEvent OnObjectHighlightEnd;
|
|
public event OnRendererHighlightEvent OnRendererHighlightStart;
|
|
|
|
public SeeThroughMode seeThrough;
|
|
[Range (0, 5f)]
|
|
public float seeThroughIntensity = 0.8f;
|
|
[Range (0, 1)]
|
|
public float seeThroughTintAlpha = 0.5f;
|
|
public Color seeThroughTintColor = Color.red;
|
|
|
|
|
|
struct ModelMaterials
|
|
{
|
|
public bool render; // if this object can render this frame
|
|
public Transform transform;
|
|
public bool bakedTransform;
|
|
public Vector3 currentPosition, currentRotation, currentScale;
|
|
public bool renderWasVisibleDuringSetup;
|
|
public Mesh mesh, originalMesh;
|
|
public Renderer renderer;
|
|
public bool skinnedMesh;
|
|
public Material[] fxMatMask, fxMatSolidColor, fxMatSeeThrough, fxMatOverlay, fxMatInnerGlow;
|
|
public Matrix4x4 renderingMatrix;
|
|
}
|
|
|
|
enum FadingState
|
|
{
|
|
FadingOut = -1,
|
|
NoFading = 0,
|
|
FadingIn = 1
|
|
}
|
|
|
|
[SerializeField, HideInInspector]
|
|
ModelMaterials[] rms;
|
|
[SerializeField, HideInInspector]
|
|
int rmsCount = 0;
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// True if there's some static children
|
|
/// </summary>
|
|
[NonSerialized]
|
|
public bool staticChildren;
|
|
#endif
|
|
|
|
[NonSerialized]
|
|
public Transform target;
|
|
|
|
// Time in which the highlight started
|
|
[NonSerialized]
|
|
public float highlightStartTime;
|
|
|
|
const string SKW_ALPHACLIP = "HP_ALPHACLIP";
|
|
const string UNIFORM_CUTOFF = "_CutOff";
|
|
const float TAU = 0.70711f;
|
|
|
|
// Reference materials. These are instanced per object (rms).
|
|
static Material fxMatMask, fxMatSolidColor, fxMatSeeThrough, fxMatOverlay;
|
|
|
|
// Per-object materials
|
|
Material fxMatGlow, fxMatInnerGlow, fxMatOutline, fxMatOccluder, fxMatTarget;
|
|
Material fxMatComposeGlow, fxMatComposeOutline, fxMatBlurGlow, fxMatBlurOutline;
|
|
|
|
static Vector3[] offsets;
|
|
|
|
float fadeStartTime;
|
|
FadingState fading = FadingState.NoFading;
|
|
CommandBuffer cbOccluder, cbMask, cbSeeThrough, cbGlow, cbOutline, cbOverlay, cbInnerGlow;
|
|
CommandBuffer cbSmoothBlend;
|
|
int[] mipGlowBuffers, mipOutlineBuffers;
|
|
int glowRT, outlineRT;
|
|
static Mesh quadMesh;
|
|
int sourceRT;
|
|
Matrix4x4 quadGlowMatrix, quadOutlineMatrix;
|
|
Vector3[] corners;
|
|
RenderTextureDescriptor sourceDesc;
|
|
Color debugColor, blackColor;
|
|
|
|
void OnEnable ()
|
|
{
|
|
debugColor = new Color (1f, 0f, 0f, 0.5f);
|
|
blackColor = new Color (0, 0, 0, 0);
|
|
if (offsets == null || offsets.Length != 8) {
|
|
offsets = new Vector3[] {
|
|
Vector3.up,
|
|
Vector3.right,
|
|
Vector3.down,
|
|
Vector3.left,
|
|
new Vector3 (-TAU, TAU, 0),
|
|
new Vector3 (TAU, TAU, 0),
|
|
new Vector3 (TAU, -TAU, 0),
|
|
new Vector3 (-TAU, -TAU, 0)
|
|
};
|
|
}
|
|
if (corners == null || corners.Length != 8) {
|
|
corners = new Vector3[8];
|
|
}
|
|
if (quadMesh == null) {
|
|
BuildQuad ();
|
|
}
|
|
if (target == null) {
|
|
target = transform;
|
|
}
|
|
if (profileSync && profile != null) {
|
|
profile.Load (this);
|
|
}
|
|
if (glowPasses == null || glowPasses.Length != 4) {
|
|
glowPasses = new GlowPassData[4];
|
|
glowPasses [0] = new GlowPassData () { offset = 4, alpha = 0.1f, color = new Color (0.64f, 1f, 0f, 1f) };
|
|
glowPasses [1] = new GlowPassData () { offset = 3, alpha = 0.2f, color = new Color (0.64f, 1f, 0f, 1f) };
|
|
glowPasses [2] = new GlowPassData () { offset = 2, alpha = 0.3f, color = new Color (0.64f, 1f, 0f, 1f) };
|
|
glowPasses [3] = new GlowPassData () { offset = 1, alpha = 0.4f, color = new Color (0.64f, 1f, 0f, 1f) };
|
|
}
|
|
sourceRT = Shader.PropertyToID ("_HPSourceRT");
|
|
|
|
CheckGeometrySupportDependencies ();
|
|
SetupMaterial ();
|
|
}
|
|
|
|
void OnDisable ()
|
|
{
|
|
UpdateMaterialProperties ();
|
|
}
|
|
|
|
|
|
void Reset ()
|
|
{
|
|
SetupMaterial ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a profile into this effect
|
|
/// </summary>
|
|
public void ProfileLoad (HighlightProfile profile)
|
|
{
|
|
if (profile != null) {
|
|
profile.Load (this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reloads currently assigned profile
|
|
/// </summary>
|
|
public void ProfileReload ()
|
|
{
|
|
if (profile != null) {
|
|
profile.Load (this);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Save current settings into given profile
|
|
/// </summary>
|
|
public void ProfileSaveChanges (HighlightProfile profile)
|
|
{
|
|
if (profile != null) {
|
|
profile.Save (this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Save current settings into current profile
|
|
/// </summary>
|
|
public void ProfileSaveChanges ()
|
|
{
|
|
if (profile != null) {
|
|
profile.Save (this);
|
|
}
|
|
}
|
|
|
|
|
|
public void Refresh ()
|
|
{
|
|
if (enabled) {
|
|
SetupMaterial ();
|
|
}
|
|
}
|
|
|
|
public void RenderOccluder ()
|
|
{
|
|
|
|
if (rms == null || fxMatOccluder == null)
|
|
return;
|
|
|
|
for (int k = 0; k < rms.Length; k++) {
|
|
Transform t = rms [k].transform;
|
|
if (t == null)
|
|
continue;
|
|
Mesh mesh = rms [k].mesh;
|
|
if (mesh == null)
|
|
continue;
|
|
if (!rms [k].renderer.isVisible)
|
|
continue;
|
|
|
|
if (rms [k].skinnedMesh) {
|
|
cbOccluder.Clear ();
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
cbOccluder.DrawRenderer (rms [k].renderer, fxMatOccluder, l);
|
|
}
|
|
Graphics.ExecuteCommandBuffer (cbOccluder);
|
|
} else {
|
|
Vector3 position = t.position;
|
|
Vector3 lossyScale = t.lossyScale;
|
|
if (rms [k].bakedTransform) {
|
|
if (rms [k].currentPosition != t.position || rms [k].currentRotation != t.eulerAngles || rms [k].currentScale != t.lossyScale) {
|
|
BakeTransform (k, true);
|
|
}
|
|
rms [k].renderingMatrix = Matrix4x4.identity;
|
|
} else {
|
|
rms [k].renderingMatrix = Matrix4x4.TRS (position, t.rotation, lossyScale);
|
|
}
|
|
Graphics.DrawMesh (mesh, rms [k].renderingMatrix, fxMatOccluder, gameObject.layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void OnRenderObject ()
|
|
{
|
|
|
|
#if UNITY_EDITOR
|
|
if (!previewInEditor && !Application.isPlaying)
|
|
return;
|
|
#endif
|
|
|
|
bool seeThroughReal = seeThroughIntensity > 0 && (this.seeThrough == SeeThroughMode.AlwaysWhenOccluded || (this.seeThrough == SeeThroughMode.WhenHighlighted && _highlighted));
|
|
if (!_highlighted && !seeThroughReal) {
|
|
return;
|
|
}
|
|
|
|
// Check camera culling mask
|
|
Camera cam = Camera.current;
|
|
int cullingMask = cam.cullingMask;
|
|
|
|
// Ensure renderers are valid and visible (in case LODgroup has changed active renderer)
|
|
if (!ignoreObjectVisibility) {
|
|
for (int k = 0; k < rmsCount; k++) {
|
|
if (rms [k].renderer != null && rms [k].renderer.isVisible != rms [k].renderWasVisibleDuringSetup) {
|
|
SetupMaterial ();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply effect
|
|
float glowReal = _highlighted ? this.glow : 0;
|
|
int layer = gameObject.layer;
|
|
|
|
if (fxMatMask == null)
|
|
return;
|
|
|
|
// Check smooth blend ztesting capability
|
|
bool useSmoothGlow = glow > 0 && glowQuality == QualityLevel.Highest;
|
|
bool useSmoothOutline = outline > 0 && outlineQuality == QualityLevel.Highest;
|
|
bool useSmoothBlend = useSmoothGlow || useSmoothOutline;
|
|
#if UNITY_EDITOR
|
|
bool glowOnTop = useSmoothGlow && (glowAlwaysOnTop || cam.cameraType == CameraType.SceneView);
|
|
bool outlineOnTop = useSmoothOutline && (outlineAlwaysOnTop || cam.cameraType == CameraType.SceneView);
|
|
#else
|
|
bool glowOnTop = useSmoothBlend && (!glowOptimalBlit || glowAlwaysOnTop);
|
|
bool outlineOnTop = useSmoothBlend && (!outlineOptimalBlit || outlineAlwaysOnTop);
|
|
#endif
|
|
if (UnityEngine.XR.XRSettings.enabled && Application.isPlaying)
|
|
{
|
|
glowOnTop = outlineOnTop = true;
|
|
}
|
|
|
|
|
|
// First create masks
|
|
for (int k = 0; k < rmsCount; k++) {
|
|
rms [k].render = false;
|
|
Transform t = rms [k].transform;
|
|
if (t == null)
|
|
continue;
|
|
Mesh mesh = rms [k].mesh;
|
|
if (mesh == null)
|
|
continue;
|
|
if (((1 << t.gameObject.layer) & cullingMask) == 0)
|
|
continue;
|
|
if (!rms [k].renderer.isVisible)
|
|
continue;
|
|
if (!reflectionProbes && cam.cameraType == CameraType.Reflection)
|
|
continue;
|
|
rms [k].render = true;
|
|
|
|
if (rms [k].skinnedMesh) {
|
|
cbMask.Clear ();
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
cbMask.DrawRenderer (rms [k].renderer, rms [k].fxMatMask [l], l);
|
|
}
|
|
Graphics.ExecuteCommandBuffer (cbMask);
|
|
} else {
|
|
Vector3 lossyScale = t.lossyScale;
|
|
Vector3 position = t.position;
|
|
if (rms [k].bakedTransform) {
|
|
if (rms [k].currentPosition != t.position || rms [k].currentRotation != t.eulerAngles || rms [k].currentScale != t.lossyScale) {
|
|
BakeTransform (k, true);
|
|
}
|
|
rms [k].renderingMatrix = Matrix4x4.identity;
|
|
} else {
|
|
rms [k].renderingMatrix = Matrix4x4.TRS (position, t.rotation, lossyScale);
|
|
}
|
|
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
if (_highlighted && (outlineOnTop || glowOnTop))
|
|
{
|
|
rms[k].fxMatMask [l].SetInt("_ZTest", (int)UnityEngine.Rendering.CompareFunction.Always);
|
|
}
|
|
else
|
|
{
|
|
rms[k].fxMatMask[l].SetInt("_ZTest", (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
}
|
|
rms [k].fxMatMask [l].SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute tweening
|
|
float fade = 1f;
|
|
if (fading != FadingState.NoFading) {
|
|
if (fading == FadingState.FadingIn) {
|
|
if (fadeInDuration > 0) {
|
|
fade = (Time.time - fadeStartTime) / fadeInDuration;
|
|
if (fade > 1f) {
|
|
fade = 1f;
|
|
fading = FadingState.NoFading;
|
|
}
|
|
}
|
|
} else if (fadeOutDuration > 0) {
|
|
fade = 1f - (Time.time - fadeStartTime) / fadeOutDuration;
|
|
if (fade < 0f) {
|
|
fade = 0f;
|
|
fading = FadingState.NoFading;
|
|
_highlighted = false;
|
|
if (OnObjectHighlightEnd != null) {
|
|
OnObjectHighlightEnd (gameObject);
|
|
}
|
|
SendMessage ("HighlightEnd", null, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (glowQuality == QualityLevel.High) {
|
|
glowReal *= 0.25f;
|
|
}
|
|
|
|
int smoothRTWidth = 0;
|
|
int smoothRTHeight = 0;
|
|
Bounds smoothBounds = new Bounds ();
|
|
|
|
if (useSmoothBlend) {
|
|
if (cam.allowMSAA && QualitySettings.antiAliasing > 1) {
|
|
glowOnTop = outlineOnTop = true;
|
|
}
|
|
// Prepare smooth outer glow / outline target
|
|
cbSmoothBlend.Clear ();
|
|
smoothRTWidth = cam.pixelWidth;
|
|
smoothRTHeight = cam.pixelHeight;
|
|
if (smoothRTHeight <= 0) {
|
|
smoothRTHeight = 1;
|
|
}
|
|
if (UnityEngine.XR.XRSettings.enabled && Application.isPlaying) {
|
|
sourceDesc = UnityEngine.XR.XRSettings.eyeTextureDesc;
|
|
} else {
|
|
sourceDesc = new RenderTextureDescriptor (smoothRTWidth, smoothRTHeight, RenderTextureFormat.Default);
|
|
sourceDesc.volumeDepth = 1;
|
|
}
|
|
sourceDesc.msaaSamples = 1;
|
|
sourceDesc.useMipMap = false;
|
|
sourceDesc.depthBufferBits = 0;
|
|
#if UNITY_EDITOR
|
|
cbSmoothBlend.GetTemporaryRT (sourceRT, sourceDesc, FilterMode.Bilinear);
|
|
if (glowOnTop || outlineOnTop || cam.cameraType == CameraType.SceneView) {
|
|
cbSmoothBlend.SetRenderTarget (sourceRT);
|
|
} else {
|
|
cbSmoothBlend.SetRenderTarget (sourceRT, BuiltinRenderTextureType.CameraTarget);
|
|
}
|
|
cbSmoothBlend.ClearRenderTarget (false, true, new Color (0, 0, 0, 0));
|
|
#else
|
|
cbSmoothBlend.GetTemporaryRT (sourceRT, sourceDesc);
|
|
if (glowOnTop || outlineOnTop) {
|
|
cbSmoothBlend.SetRenderTarget (sourceRT);
|
|
} else {
|
|
cbSmoothBlend.SetRenderTarget (sourceRT, BuiltinRenderTextureType.CameraTarget);
|
|
}
|
|
cbSmoothBlend.ClearRenderTarget (false, true, new Color (0, 0, 0, 0));
|
|
#endif
|
|
}
|
|
|
|
// Add effects
|
|
for (int k = 0; k < rmsCount; k++) {
|
|
if (!rms [k].render)
|
|
continue;
|
|
Mesh mesh = rms [k].mesh;
|
|
|
|
// See-Through
|
|
if (seeThroughReal) {
|
|
if (rms [k].skinnedMesh) {
|
|
cbSeeThrough.Clear ();
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
if (l < rms [k].fxMatSeeThrough.Length && rms [k].fxMatSeeThrough [l] != null) {
|
|
cbSeeThrough.DrawRenderer (rms [k].renderer, rms [k].fxMatSeeThrough [l], l);
|
|
}
|
|
}
|
|
Graphics.ExecuteCommandBuffer (cbSeeThrough);
|
|
} else {
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
if (l < rms [k].fxMatSeeThrough.Length && rms [k].fxMatSeeThrough [l] != null) {
|
|
rms [k].fxMatSeeThrough [l].SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_highlighted)
|
|
continue;
|
|
|
|
if (useSmoothBlend) {
|
|
if (k == 0) {
|
|
smoothBounds = rms [k].renderer.bounds;
|
|
} else {
|
|
smoothBounds.Encapsulate (rms [k].renderer.bounds);
|
|
}
|
|
}
|
|
|
|
for (int l = 0; l < mesh.subMeshCount; l++) {
|
|
|
|
// Render object body for glow/outline highest quality
|
|
if (useSmoothBlend) {
|
|
if (l < rms [k].fxMatSolidColor.Length) {
|
|
if (rms [k].skinnedMesh) {
|
|
cbSmoothBlend.DrawRenderer (rms [k].renderer, rms [k].fxMatSolidColor [l], l);
|
|
} else {
|
|
cbSmoothBlend.DrawMesh (mesh, rms [k].renderingMatrix, rms [k].fxMatSolidColor [l], l);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Glow
|
|
if (glow > 0 && glowQuality != QualityLevel.Highest) {
|
|
fxMatGlow.SetVector ("_GlowDirection", Vector3.zero);
|
|
for (int j = 0; j < glowPasses.Length; j++) {
|
|
fxMatGlow.SetColor ("_GlowColor", glowPasses [j].color);
|
|
fxMatGlow.SetVector ("_Glow", new Vector4 (fade * glowReal * glowPasses [j].alpha, glowPasses [j].offset * glowWidth / 100f, glowMagicNumber1, glowMagicNumber2));
|
|
if (glowQuality == QualityLevel.High) {
|
|
for (int o = 0; o < offsets.Length; o++) {
|
|
Vector3 direction = offsets [o];
|
|
direction.y *= cam.aspect;
|
|
fxMatGlow.SetVector ("_GlowDirection", direction);
|
|
|
|
if (rms [k].skinnedMesh) {
|
|
cbGlow.Clear ();
|
|
cbGlow.DrawRenderer (rms [k].renderer, fxMatGlow, l);
|
|
Graphics.ExecuteCommandBuffer (cbGlow);
|
|
} else {
|
|
fxMatGlow.SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
} else {
|
|
if (rms [k].skinnedMesh) {
|
|
cbGlow.Clear ();
|
|
cbGlow.DrawRenderer (rms [k].renderer, fxMatGlow, l);
|
|
Graphics.ExecuteCommandBuffer (cbGlow);
|
|
} else {
|
|
fxMatGlow.SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Outline
|
|
if (outline > 0 && outlineQuality != QualityLevel.Highest) {
|
|
Color outlineColor = this.outlineColor;
|
|
outlineColor.a = outline * fade;
|
|
fxMatOutline.SetColor ("_OutlineColor", outlineColor);
|
|
if (outlineQuality == QualityLevel.High) {
|
|
for (int o = 0; o < offsets.Length; o++) {
|
|
Vector3 direction = offsets [o] * (outlineWidth / 100f);
|
|
direction.y *= cam.aspect;
|
|
fxMatOutline.SetVector ("_OutlineDirection", direction);
|
|
if (rms [k].skinnedMesh) {
|
|
cbOutline.Clear ();
|
|
cbOutline.DrawRenderer (rms [k].renderer, fxMatOutline, l);
|
|
Graphics.ExecuteCommandBuffer (cbOutline);
|
|
} else {
|
|
fxMatOutline.SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
} else {
|
|
if (rms [k].skinnedMesh) {
|
|
cbOutline.Clear ();
|
|
cbOutline.DrawRenderer (rms [k].renderer, fxMatOutline, l);
|
|
Graphics.ExecuteCommandBuffer (cbOutline);
|
|
} else {
|
|
fxMatOutline.SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Overlay
|
|
if (overlay > 0) {
|
|
if (l < rms [k].fxMatOverlay.Length && rms [k].fxMatOverlay [l] != null) {
|
|
Color overlayColor = this.overlayColor;
|
|
overlayColor.a = overlay * fade;
|
|
rms [k].fxMatOverlay [l].color = overlayColor;
|
|
rms [k].fxMatOverlay [l].SetVector ("_OverlayData", new Vector3 (overlayAnimationSpeed, overlayMinIntensity, overlayBlending));
|
|
|
|
if (rms [k].skinnedMesh) {
|
|
cbOverlay.Clear ();
|
|
cbOverlay.DrawRenderer (rms [k].renderer, rms [k].fxMatOverlay [l], l);
|
|
Graphics.ExecuteCommandBuffer (cbOverlay);
|
|
} else {
|
|
rms [k].fxMatOverlay [l].SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
// Inner Glow
|
|
if (innerGlow > 0 && innerGlowWidth > 0) {
|
|
if (l < rms [k].fxMatInnerGlow.Length && rms [k].fxMatInnerGlow [l] != null) {
|
|
Color innerGlowColorA = innerGlowColor;
|
|
innerGlowColorA.a = innerGlow * fade;
|
|
rms [k].fxMatInnerGlow [l].SetColor ("_Color", innerGlowColorA);
|
|
|
|
if (rms [k].skinnedMesh) {
|
|
cbInnerGlow.Clear ();
|
|
cbInnerGlow.DrawRenderer (rms [k].renderer, rms [k].fxMatInnerGlow [l], l);
|
|
Graphics.ExecuteCommandBuffer (cbInnerGlow);
|
|
} else {
|
|
rms [k].fxMatInnerGlow [l].SetPass (0);
|
|
Graphics.DrawMeshNow (mesh, rms [k].renderingMatrix, l);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Target
|
|
if (targetFX) {
|
|
float fadeOut = 1f;
|
|
if (Application.isPlaying) {
|
|
fadeOut = (Time.time - highlightStartTime);
|
|
if (fadeOut >= targetFXStayDuration) {
|
|
fadeOut -= targetFXStayDuration;
|
|
fadeOut = 1f - fadeOut;
|
|
}
|
|
if (fadeOut > 1f) {
|
|
fadeOut = 1f;
|
|
}
|
|
}
|
|
if (fadeOut > 0) {
|
|
float scaleT = 1f;
|
|
float time;
|
|
if (Application.isPlaying) {
|
|
scaleT = (Time.time - highlightStartTime) / targetFXTransitionDuration;
|
|
if (scaleT > 1f) {
|
|
scaleT = 1f;
|
|
}
|
|
scaleT = Mathf.Sin (scaleT * Mathf.PI * 0.5f);
|
|
time = Time.time;
|
|
} else {
|
|
time = (float)DateTime.Now.Subtract (DateTime.Today).TotalSeconds;
|
|
}
|
|
Bounds bounds = rms [k].renderer.bounds;
|
|
Vector3 size = bounds.size;
|
|
float minSize = size.x;
|
|
if (size.y < minSize) {
|
|
minSize = size.y;
|
|
}
|
|
if (size.z < minSize) {
|
|
minSize = size.z;
|
|
}
|
|
size.x = size.y = size.z = minSize;
|
|
size = Vector3.Lerp (size * targetFXInitialScale, size * targetFXEndScale, scaleT);
|
|
Quaternion camRot = Quaternion.LookRotation (cam.transform.position - rms [k].transform.position);
|
|
Quaternion rotation = Quaternion.Euler (0, 0, time * targetFXRotationSpeed);
|
|
camRot *= rotation;
|
|
Vector3 center = targetFXCenter != null ? targetFXCenter.transform.position : bounds.center;
|
|
Matrix4x4 m = Matrix4x4.TRS (center, camRot, size);
|
|
Color color = targetFXColor;
|
|
color.a *= fade * fadeOut;
|
|
fxMatTarget.color = color;
|
|
fxMatTarget.SetPass (0);
|
|
Graphics.DrawMeshNow (quadMesh, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (useSmoothBlend) {
|
|
if (ComputeSmoothQuadMatrix (cam, smoothBounds)) {
|
|
// Smooth Glow
|
|
if (useSmoothGlow) {
|
|
float intensity = glow * fade;
|
|
fxMatComposeGlow.color = new Color (glowHQColor.r * intensity, glowHQColor.g * intensity, glowHQColor.b * intensity, glowHQColor.a * intensity);
|
|
SmoothGlow (cam, sourceRT, smoothRTWidth / glowDownsampling, smoothRTHeight / glowDownsampling);
|
|
}
|
|
|
|
// Smooth Outline
|
|
if (useSmoothOutline) {
|
|
fxMatComposeOutline.color = new Color (outlineColor.r, outlineColor.g, outlineColor.b, 5f * outlineColor.a * outline * fade);
|
|
SmoothOutline (cam, sourceRT, smoothRTWidth / outlineDownsampling, smoothRTHeight / outlineDownsampling);
|
|
}
|
|
|
|
// Bit result
|
|
ComposeSmoothBlend (glowOnTop, outlineOnTop);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ComputeSmoothQuadMatrix (Camera cam, Bounds bounds)
|
|
{
|
|
// Compute bounds in screen space and enlarge for glow space
|
|
Vector3 min = bounds.min;
|
|
Vector3 max = bounds.max;
|
|
corners [0] = new Vector3 (min.x, min.y, min.z);
|
|
corners [1] = new Vector3 (min.x, min.y, max.z);
|
|
corners [2] = new Vector3 (max.x, min.y, min.z);
|
|
corners [3] = new Vector3 (max.x, min.y, max.z);
|
|
corners [4] = new Vector3 (min.x, max.y, min.z);
|
|
corners [5] = new Vector3 (min.x, max.y, max.z);
|
|
corners [6] = new Vector3 (max.x, max.y, min.z);
|
|
corners [7] = new Vector3 (max.x, max.y, max.z);
|
|
Vector3 scrMin = new Vector3 (float.MaxValue, float.MaxValue, 0);
|
|
Vector3 scrMax = new Vector3 (float.MinValue, float.MinValue, 0);
|
|
float distance = float.MaxValue;
|
|
for (int k = 0; k < corners.Length; k++) {
|
|
corners [k] = cam.WorldToScreenPoint (corners [k]);
|
|
if (corners [k].x < scrMin.x) {
|
|
scrMin.x = corners [k].x;
|
|
}
|
|
if (corners [k].y < scrMin.y) {
|
|
scrMin.y = corners [k].y;
|
|
}
|
|
if (corners [k].x > scrMax.x) {
|
|
scrMax.x = corners [k].x;
|
|
}
|
|
if (corners [k].y > scrMax.y) {
|
|
scrMax.y = corners [k].y;
|
|
}
|
|
if (corners [k].z < distance) {
|
|
distance = corners [k].z;
|
|
if (distance < cam.nearClipPlane)
|
|
{
|
|
scrMin.x = scrMin.y = 0;
|
|
scrMax.x = cam.pixelWidth;
|
|
scrMax.y = cam.pixelHeight;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (scrMax.y == scrMin.y)
|
|
return false;
|
|
|
|
if (distance < cam.nearClipPlane) {
|
|
distance = cam.nearClipPlane + 0.01f;
|
|
}
|
|
scrMin.z = scrMax.z = distance;
|
|
if (outline > 0) {
|
|
BuildMatrix (cam, scrMin, scrMax, (int)(10 + 2 * outlineWidth), ref quadOutlineMatrix);
|
|
}
|
|
if (glow > 0) {
|
|
BuildMatrix (cam, scrMin, scrMax, (int)(20 + 30 * glowWidth + 10 * glowDownsampling), ref quadGlowMatrix);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void BuildMatrix (Camera cam, Vector3 scrMin, Vector3 scrMax, int border, ref Matrix4x4 quadMatrix)
|
|
{
|
|
|
|
// Insert padding to make room for effects
|
|
scrMin.x -= border;
|
|
scrMin.y -= border;
|
|
scrMax.x += border;
|
|
scrMax.y += border;
|
|
|
|
// Back to world space
|
|
Vector3 third = new Vector3 (scrMax.x, scrMin.y, scrMin.z);
|
|
scrMin = cam.ScreenToWorldPoint (scrMin);
|
|
scrMax = cam.ScreenToWorldPoint (scrMax);
|
|
third = cam.ScreenToWorldPoint (third);
|
|
|
|
float width = Vector3.Distance (scrMin, third);
|
|
float height = Vector3.Distance (scrMax, third);
|
|
|
|
quadMatrix = Matrix4x4.TRS ((scrMin + scrMax) * 0.5f, cam.transform.rotation, new Vector3 (width, height, 1f));
|
|
}
|
|
|
|
void SmoothGlow (Camera cam, int sourceRT, int rtWidth, int rtHeight)
|
|
{
|
|
const int blurPasses = 4;
|
|
|
|
// Blur buffers
|
|
int bufferCount = blurPasses * 2;
|
|
if (mipGlowBuffers == null || mipGlowBuffers.Length != bufferCount) {
|
|
mipGlowBuffers = new int[bufferCount];
|
|
for (int k = 0; k < bufferCount; k++) {
|
|
mipGlowBuffers [k] = Shader.PropertyToID ("_HPSmoothGlowTemp" + k);
|
|
}
|
|
glowRT = Shader.PropertyToID ("_HPComposeGlowFinal");
|
|
mipGlowBuffers [bufferCount - 2] = glowRT;
|
|
}
|
|
RenderTextureDescriptor glowDesc = sourceDesc;
|
|
glowDesc.depthBufferBits = 0;
|
|
if (glowDesc.vrUsage == VRTextureUsage.TwoEyes) {
|
|
glowDesc.vrUsage = VRTextureUsage.None;
|
|
fxMatBlurGlow.SetFloat ("_StereoRendering", 0.5f);
|
|
fxMatComposeGlow.SetFloat ("_StereoRendering", 0.5f);
|
|
} else {
|
|
fxMatBlurGlow.SetFloat ("_StereoRendering", 1f);
|
|
fxMatComposeGlow.SetFloat ("_StereoRendering", 1f);
|
|
}
|
|
|
|
for (int k = 0; k < bufferCount; k++) {
|
|
float reduction = k / 2 + 2;
|
|
int reducedWidth = (int)(rtWidth / reduction);
|
|
int reducedHeight = (int)(rtHeight / reduction);
|
|
if (reducedWidth <= 0) {
|
|
reducedWidth = 1;
|
|
}
|
|
if (reducedHeight <= 0) {
|
|
reducedHeight = 1;
|
|
}
|
|
glowDesc.width = reducedWidth;
|
|
glowDesc.height = reducedHeight;
|
|
cbSmoothBlend.GetTemporaryRT (mipGlowBuffers [k], glowDesc, FilterMode.Bilinear);
|
|
}
|
|
|
|
for (int k = 0; k < bufferCount - 1; k += 2) {
|
|
if (k == 0) {
|
|
cbSmoothBlend.Blit (sourceRT, mipGlowBuffers [k + 1], fxMatBlurGlow, 0);
|
|
} else {
|
|
cbSmoothBlend.Blit (mipGlowBuffers [k], mipGlowBuffers [k + 1], fxMatBlurGlow, 0);
|
|
}
|
|
cbSmoothBlend.Blit (mipGlowBuffers [k + 1], mipGlowBuffers [k], fxMatBlurGlow, 1);
|
|
|
|
if (k < bufferCount - 2) {
|
|
cbSmoothBlend.Blit (mipGlowBuffers [k], mipGlowBuffers [k + 2], fxMatBlurGlow, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SmoothOutline (Camera cam, int sourceRT, int rtWidth, int rtHeight)
|
|
{
|
|
int bufferCount = 2;
|
|
if (mipOutlineBuffers == null || mipOutlineBuffers.Length != bufferCount) {
|
|
mipOutlineBuffers = new int[bufferCount];
|
|
for (int k = 0; k < bufferCount; k++) {
|
|
mipOutlineBuffers [k] = Shader.PropertyToID ("_HPSmoothOutlineTemp" + k);
|
|
}
|
|
outlineRT = Shader.PropertyToID ("_HPComposeOutlineFinal");
|
|
mipOutlineBuffers [bufferCount - 1] = outlineRT;
|
|
}
|
|
RenderTextureDescriptor outlineDesc = sourceDesc;
|
|
outlineDesc.width = rtWidth;
|
|
outlineDesc.height = rtHeight;
|
|
outlineDesc.depthBufferBits = 0;
|
|
for (int k = 0; k < bufferCount; k++) {
|
|
cbSmoothBlend.GetTemporaryRT (mipOutlineBuffers [k], outlineDesc, FilterMode.Bilinear);
|
|
}
|
|
|
|
cbSmoothBlend.Blit (sourceRT, mipOutlineBuffers [0], fxMatBlurOutline, 0);
|
|
cbSmoothBlend.Blit (mipOutlineBuffers [0], mipOutlineBuffers [1], fxMatBlurOutline, 1);
|
|
}
|
|
|
|
void ComposeSmoothBlend (bool glowOnTop, bool outlineOnTop)
|
|
{
|
|
bool renderSmoothGlow = glow > 0 && glowQuality == QualityLevel.Highest;
|
|
if (renderSmoothGlow) {
|
|
fxMatComposeGlow.SetVector ("_Flip", (UnityEngine.XR.XRSettings.enabled && flipY) ? new Vector3 (1, -1, 0) : new Vector3 (0, 1, 0));
|
|
if (glowOptimalBlit) {
|
|
cbSmoothBlend.SetRenderTarget (BuiltinRenderTextureType.CameraTarget);
|
|
fxMatComposeGlow.SetInt ("_ZTest", glowOnTop ? (int)UnityEngine.Rendering.CompareFunction.Always : (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
fxMatComposeGlow.SetColor ("_Debug", glowBlitDebug ? debugColor : blackColor);
|
|
cbSmoothBlend.DrawMesh (quadMesh, quadGlowMatrix, fxMatComposeGlow, 0, 0);
|
|
} else {
|
|
cbSmoothBlend.Blit (glowRT, BuiltinRenderTextureType.CameraTarget, fxMatComposeGlow, 1);
|
|
}
|
|
}
|
|
bool renderSmoothOutline = outline > 0 && outlineQuality == QualityLevel.Highest;
|
|
if (renderSmoothOutline) {
|
|
fxMatComposeOutline.SetVector ("_Flip", (UnityEngine.XR.XRSettings.enabled && flipY) ? new Vector3 (1, -1, 0) : new Vector3 (0, 1, 0));
|
|
if (outlineOptimalBlit) {
|
|
cbSmoothBlend.SetRenderTarget (BuiltinRenderTextureType.CameraTarget);
|
|
fxMatComposeOutline.SetInt ("_ZTest", outlineOnTop ? (int)UnityEngine.Rendering.CompareFunction.Always : (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
fxMatComposeOutline.SetColor ("_Debug", outlineBlitDebug ? debugColor : blackColor);
|
|
cbSmoothBlend.DrawMesh (quadMesh, quadOutlineMatrix, fxMatComposeOutline, 0, 0);
|
|
} else {
|
|
cbSmoothBlend.Blit (outlineRT, BuiltinRenderTextureType.CameraTarget, fxMatComposeOutline, 1);
|
|
}
|
|
}
|
|
// Release render textures
|
|
if (renderSmoothGlow) {
|
|
for (int k = 0; k < mipGlowBuffers.Length; k++) {
|
|
cbSmoothBlend.ReleaseTemporaryRT (mipGlowBuffers [k]);
|
|
}
|
|
}
|
|
if (renderSmoothOutline) {
|
|
for (int k = 0; k < mipOutlineBuffers.Length; k++) {
|
|
cbSmoothBlend.ReleaseTemporaryRT (mipOutlineBuffers [k]);
|
|
}
|
|
}
|
|
|
|
cbSmoothBlend.ReleaseTemporaryRT (sourceRT);
|
|
|
|
Graphics.ExecuteCommandBuffer (cbSmoothBlend);
|
|
}
|
|
|
|
void InitMaterial (ref Material material, string shaderName)
|
|
{
|
|
if (material == null) {
|
|
Shader shaderFX = Shader.Find (shaderName);
|
|
if (shaderFX == null) {
|
|
Debug.LogError ("Shader " + shaderName + " not found.");
|
|
enabled = false;
|
|
return;
|
|
}
|
|
material = new Material (shaderFX);
|
|
}
|
|
}
|
|
|
|
public void SetTarget (Transform transform)
|
|
{
|
|
if (transform == target || transform == null)
|
|
return;
|
|
|
|
if (_highlighted) {
|
|
SetHighlighted (false);
|
|
}
|
|
|
|
target = transform;
|
|
SetupMaterial ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start or finish highlight on the object
|
|
/// </summary>
|
|
public void SetHighlighted (bool state)
|
|
{
|
|
|
|
if (!Application.isPlaying) {
|
|
_highlighted = state;
|
|
return;
|
|
}
|
|
|
|
if (fading == FadingState.NoFading) {
|
|
fadeStartTime = Time.time;
|
|
}
|
|
|
|
if (state && !ignore) {
|
|
if (_highlighted && fading == FadingState.NoFading) {
|
|
return;
|
|
}
|
|
if (OnObjectHighlightStart != null) {
|
|
if (!OnObjectHighlightStart (gameObject)) {
|
|
return;
|
|
}
|
|
}
|
|
SendMessage ("HighlightStart", null, SendMessageOptions.DontRequireReceiver);
|
|
highlightStartTime = Time.time;
|
|
if (fadeInDuration > 0) {
|
|
if (fading == FadingState.FadingOut) {
|
|
float remaining = fadeOutDuration - (Time.time - fadeStartTime);
|
|
fadeStartTime = Time.time - remaining;
|
|
}
|
|
fading = FadingState.FadingIn;
|
|
} else {
|
|
fading = FadingState.NoFading;
|
|
}
|
|
_highlighted = true;
|
|
UpdateMaterialProperties (); // call update material properties instead
|
|
} else if (_highlighted) {
|
|
if (fadeOutDuration > 0) {
|
|
if (fading == FadingState.FadingIn) {
|
|
float elapsed = Time.time - fadeStartTime;
|
|
fadeStartTime = Time.time + elapsed - fadeInDuration;
|
|
}
|
|
fading = FadingState.FadingOut; // when fade out ends, highlighted will be set to false in OnRenderObject
|
|
} else {
|
|
fading = FadingState.NoFading;
|
|
_highlighted = false;
|
|
if (OnObjectHighlightEnd != null) {
|
|
OnObjectHighlightEnd (gameObject);
|
|
}
|
|
SendMessage ("HighlightEnd", null, SendMessageOptions.DontRequireReceiver);
|
|
UpdateMaterialProperties ();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetupMaterial ()
|
|
{
|
|
|
|
#if UNITY_EDITOR
|
|
staticChildren = false;
|
|
#endif
|
|
|
|
if (target == null || fxMatMask == null)
|
|
return;
|
|
|
|
Renderer[] rr = null;
|
|
switch (effectGroup) {
|
|
case TargetOptions.OnlyThisObject:
|
|
Renderer renderer = target.GetComponent<Renderer> ();
|
|
if (renderer != null) {
|
|
rr = new Renderer[1];
|
|
rr [0] = renderer;
|
|
}
|
|
break;
|
|
case TargetOptions.RootToChildren:
|
|
Transform root = target;
|
|
while (root.parent != null) {
|
|
root = root.parent;
|
|
}
|
|
rr = root.GetComponentsInChildren<Renderer> ();
|
|
break;
|
|
case TargetOptions.Layer:
|
|
HighlightEffect eg = this;
|
|
if (target != transform) {
|
|
HighlightEffect targetEffect = target.GetComponent<HighlightEffect> ();
|
|
if (targetEffect != null) {
|
|
eg = targetEffect;
|
|
}
|
|
}
|
|
rr = FindRenderersWithLayer (eg.effectGroupLayer);
|
|
break;
|
|
default:
|
|
rr = target.GetComponentsInChildren<Renderer> ();
|
|
break;
|
|
}
|
|
|
|
if (rr == null) {
|
|
rr = new Renderer[0];
|
|
}
|
|
if (rms == null || rms.Length < rr.Length) {
|
|
rms = new ModelMaterials[rr.Length];
|
|
}
|
|
|
|
rmsCount = 0;
|
|
for (int k = 0; k < rr.Length; k++) {
|
|
rms [rmsCount] = new ModelMaterials ();
|
|
Renderer renderer = rr [k];
|
|
rms [rmsCount].renderer = renderer;
|
|
rms [rmsCount].renderWasVisibleDuringSetup = renderer.isVisible;
|
|
|
|
|
|
if (renderer.transform != target) {
|
|
HighlightEffect otherEffect = renderer.GetComponent<HighlightEffect> ();
|
|
if (otherEffect != null && otherEffect.enabled) {
|
|
continue; // independent subobject
|
|
}
|
|
}
|
|
|
|
if (OnRendererHighlightStart != null) {
|
|
if (!OnRendererHighlightStart (renderer)) {
|
|
rmsCount++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (renderer is SkinnedMeshRenderer) {
|
|
// ignore cloth skinned renderers
|
|
rms [rmsCount].skinnedMesh = true;
|
|
rms [rmsCount].mesh = ((SkinnedMeshRenderer)renderer).sharedMesh;
|
|
CheckCommandBuffers ();
|
|
} else if (Application.isPlaying && renderer.isPartOfStaticBatch) {
|
|
// static batched objects need to have a mesh collider in order to use its original mesh
|
|
MeshCollider mc = renderer.GetComponent<MeshCollider> ();
|
|
if (mc != null) {
|
|
rms [rmsCount].mesh = mc.sharedMesh;
|
|
}
|
|
} else {
|
|
MeshFilter mf = renderer.GetComponent<MeshFilter> ();
|
|
if (mf != null) {
|
|
rms [rmsCount].mesh = mf.sharedMesh;
|
|
|
|
#if UNITY_EDITOR
|
|
if (renderer.gameObject.isStatic && renderer.GetComponent<MeshCollider> () == null) {
|
|
staticChildren = true;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
if (rms [rmsCount].mesh == null) {
|
|
continue;
|
|
}
|
|
|
|
rms [rmsCount].transform = renderer.transform;
|
|
rms [rmsCount].fxMatMask = Fork (fxMatMask, rms [rmsCount].mesh);
|
|
rms [rmsCount].fxMatSeeThrough = Fork (fxMatSeeThrough, rms [rmsCount].mesh);
|
|
rms [rmsCount].fxMatOverlay = Fork (fxMatOverlay, rms [rmsCount].mesh);
|
|
rms [rmsCount].fxMatInnerGlow = Fork (fxMatInnerGlow, rms [rmsCount].mesh);
|
|
rms [rmsCount].fxMatSolidColor = Fork (fxMatSolidColor, rms [rmsCount].mesh);
|
|
rms [rmsCount].originalMesh = rms [rmsCount].mesh;
|
|
if (!rms [rmsCount].skinnedMesh) {
|
|
AverageNormals (rmsCount);
|
|
// check if scale is negative
|
|
BakeTransform (rmsCount, true);
|
|
}
|
|
rmsCount++;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// Avoids command buffer issue when refreshing asset inside the Editor
|
|
if (!Application.isPlaying) {
|
|
mipGlowBuffers = null;
|
|
mipOutlineBuffers = null;
|
|
}
|
|
#endif
|
|
|
|
UpdateMaterialProperties ();
|
|
}
|
|
|
|
List<Renderer> tempRR;
|
|
|
|
Renderer[] FindRenderersWithLayer (LayerMask layer)
|
|
{
|
|
GameObject[] gos = FindObjectsOfType<GameObject> ();
|
|
if (tempRR == null) {
|
|
tempRR = new List<Renderer> ();
|
|
} else {
|
|
tempRR.Clear ();
|
|
}
|
|
for (var i = 0; i < gos.Length; i++) {
|
|
if (((1 << gos [i].layer) & layer) != 0) {
|
|
Renderer r = gos [i].GetComponent<Renderer> ();
|
|
if (r != null) {
|
|
tempRR.Add (r);
|
|
}
|
|
}
|
|
}
|
|
return tempRR.ToArray ();
|
|
}
|
|
|
|
void CheckGeometrySupportDependencies ()
|
|
{
|
|
InitMaterial (ref fxMatMask, "HighlightPlus/Geometry/Mask");
|
|
InitMaterial (ref fxMatGlow, "HighlightPlus/Geometry/Glow");
|
|
InitMaterial (ref fxMatInnerGlow, "HighlightPlus/Geometry/InnerGlow");
|
|
InitMaterial (ref fxMatOutline, "HighlightPlus/Geometry/Outline");
|
|
InitMaterial (ref fxMatOverlay, "HighlightPlus/Geometry/Overlay");
|
|
InitMaterial (ref fxMatSeeThrough, "HighlightPlus/Geometry/SeeThrough");
|
|
InitMaterial (ref fxMatOccluder, "HighlightPlus/Geometry/SeeThroughOccluder");
|
|
InitMaterial (ref fxMatTarget, "HighlightPlus/Geometry/Target");
|
|
InitMaterial (ref fxMatComposeGlow, "HighlightPlus/Geometry/ComposeGlow");
|
|
InitMaterial (ref fxMatComposeOutline, "HighlightPlus/Geometry/ComposeOutline");
|
|
InitMaterial (ref fxMatSolidColor, "HighlightPlus/Geometry/SolidColor");
|
|
InitMaterial (ref fxMatBlurGlow, "HighlightPlus/Geometry/BlurGlow");
|
|
InitMaterial (ref fxMatBlurOutline, "HighlightPlus/Geometry/BlurOutline");
|
|
}
|
|
|
|
void CheckCommandBuffers ()
|
|
{
|
|
if (cbOccluder == null) {
|
|
cbOccluder = new CommandBuffer ();
|
|
cbOccluder.name = "Occluder";
|
|
}
|
|
if (cbMask == null) {
|
|
cbMask = new CommandBuffer ();
|
|
cbMask.name = "Mask";
|
|
}
|
|
if (cbSeeThrough == null) {
|
|
cbSeeThrough = new CommandBuffer ();
|
|
cbSeeThrough.name = "See Through";
|
|
}
|
|
if (cbGlow == null) {
|
|
cbGlow = new CommandBuffer ();
|
|
cbGlow.name = "Outer Glow";
|
|
}
|
|
if (cbOutline == null) {
|
|
cbOutline = new CommandBuffer ();
|
|
cbOutline.name = "Outline";
|
|
}
|
|
if (cbOverlay == null) {
|
|
cbOverlay = new CommandBuffer ();
|
|
cbOverlay.name = "Overlay";
|
|
}
|
|
if (cbInnerGlow == null) {
|
|
cbInnerGlow = new CommandBuffer ();
|
|
cbInnerGlow.name = "Inner Glow";
|
|
}
|
|
}
|
|
|
|
void CheckBlurCommandBuffer ()
|
|
{
|
|
if (cbSmoothBlend == null) {
|
|
cbSmoothBlend = new CommandBuffer ();
|
|
cbSmoothBlend.name = "Smooth Blend";
|
|
}
|
|
}
|
|
|
|
Material[] Fork (Material mat, Mesh mesh)
|
|
{
|
|
if (mesh == null)
|
|
return null;
|
|
int count = mesh.subMeshCount;
|
|
Material[] mm = new Material[count];
|
|
for (int k = 0; k < count; k++) {
|
|
mm [k] = Instantiate<Material> (mat);
|
|
}
|
|
return mm;
|
|
}
|
|
|
|
void BakeTransform (int objIndex, bool duplicateMesh)
|
|
{
|
|
if (rms [objIndex].mesh == null)
|
|
return;
|
|
Transform t = rms [objIndex].transform;
|
|
Vector3 scale = t.localScale;
|
|
if (scale.x >= 0 && scale.y >= 0 && scale.z >= 0) {
|
|
rms [objIndex].bakedTransform = false;
|
|
return;
|
|
}
|
|
// Duplicates mesh and bake rotation
|
|
Mesh fixedMesh = duplicateMesh ? Instantiate<Mesh> (rms [objIndex].originalMesh) : rms [objIndex].mesh;
|
|
Vector3[] vertices = fixedMesh.vertices;
|
|
for (int k = 0; k < vertices.Length; k++) {
|
|
vertices [k] = t.TransformPoint (vertices [k]);
|
|
}
|
|
fixedMesh.vertices = vertices;
|
|
Vector3[] normals = fixedMesh.normals;
|
|
if (normals != null) {
|
|
for (int k = 0; k < normals.Length; k++) {
|
|
normals [k] = t.TransformVector (normals [k]).normalized;
|
|
}
|
|
fixedMesh.normals = normals;
|
|
}
|
|
fixedMesh.RecalculateBounds ();
|
|
rms [objIndex].mesh = fixedMesh;
|
|
rms [objIndex].bakedTransform = true;
|
|
rms [objIndex].currentPosition = t.position;
|
|
rms [objIndex].currentRotation = t.eulerAngles;
|
|
rms [objIndex].currentScale = t.lossyScale;
|
|
}
|
|
|
|
|
|
void UpdateMaterialProperties ()
|
|
{
|
|
|
|
if (rms == null)
|
|
return;
|
|
|
|
if (ignore) {
|
|
_highlighted = false;
|
|
}
|
|
|
|
Color seeThroughTintColor = this.seeThroughTintColor;
|
|
seeThroughTintColor.a = this.seeThroughTintAlpha;
|
|
|
|
|
|
if (outlineWidth < 0) {
|
|
outlineWidth = 0;
|
|
}
|
|
if (glowWidth < 0) {
|
|
glowWidth = 0;
|
|
}
|
|
if (overlay < overlayMinIntensity) {
|
|
overlay = overlayMinIntensity;
|
|
}
|
|
if (targetFXTransitionDuration <= 0) {
|
|
targetFXTransitionDuration = 0.0001f;
|
|
}
|
|
if (targetFXStayDuration <= 0) {
|
|
targetFXStayDuration = 0.0001f;
|
|
}
|
|
|
|
bool useSmoothGlow = glow > 0 && glowQuality == QualityLevel.Highest;
|
|
if (useSmoothGlow) {
|
|
CheckBlurCommandBuffer ();
|
|
fxMatComposeGlow.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
fxMatBlurGlow.SetFloat ("_BlurScale", glowWidth / glowDownsampling);
|
|
fxMatBlurGlow.SetFloat ("_Speed", glowAnimationSpeed);
|
|
}
|
|
|
|
bool useSmoothOutline = outline > 0 && outlineQuality == QualityLevel.Highest;
|
|
if (useSmoothOutline) {
|
|
CheckBlurCommandBuffer ();
|
|
fxMatComposeOutline.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
fxMatBlurOutline.SetFloat ("_BlurScale", outlineWidth / outlineDownsampling);
|
|
}
|
|
|
|
// Setup materials
|
|
for (int k = 0; k < rmsCount; k++) {
|
|
if (rms [k].mesh != null) {
|
|
// Outline
|
|
float scaledOutlineWidth = outlineQuality == QualityLevel.High ? 0f : outlineWidth / 100f;
|
|
fxMatOutline.SetFloat ("_OutlineWidth", scaledOutlineWidth);
|
|
fxMatOutline.SetVector ("_OutlineDirection", Vector3.zero);
|
|
fxMatOutline.SetInt ("_OutlineZTest", outlineAlwaysOnTop ? (int)UnityEngine.Rendering.CompareFunction.Always : (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
fxMatOutline.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
fxMatOutline.SetFloat ("_ConstantWidth", constantWidth ? 1.0f : 0);
|
|
|
|
// Glow
|
|
fxMatGlow.SetVector ("_Glow2", new Vector3 (outlineWidth / 100f, glowAnimationSpeed, glowDithering ? 0 : 1));
|
|
fxMatGlow.SetInt ("_GlowZTest", glowAlwaysOnTop ? (int)UnityEngine.Rendering.CompareFunction.Always : (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
fxMatGlow.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
fxMatGlow.SetFloat ("_ConstantWidth", constantWidth ? 1.0f : 0);
|
|
|
|
// Target
|
|
if (targetFX) {
|
|
if (targetFXTexture == null) {
|
|
targetFXTexture = Resources.Load<Texture2D> ("HighlightPlus/target");
|
|
}
|
|
fxMatTarget.mainTexture = targetFXTexture;
|
|
}
|
|
|
|
// Mask, See-through & Overlay per submesh
|
|
for (int l = 0; l < rms [k].mesh.subMeshCount; l++) {
|
|
Renderer renderer = rms [k].renderer;
|
|
if (renderer == null)
|
|
continue;
|
|
|
|
Material mat = null;
|
|
if (renderer.sharedMaterials != null && l < renderer.sharedMaterials.Length) {
|
|
mat = renderer.sharedMaterials [l];
|
|
}
|
|
if (mat == null)
|
|
continue;
|
|
|
|
bool hasTexture = mat.HasProperty ("_MainTex");
|
|
bool useAlphaTest = alphaCutOff > 0 && hasTexture;
|
|
|
|
// Mask
|
|
Material fxMat = rms [k].fxMatMask [l];
|
|
if (fxMat != null) {
|
|
if (hasTexture) {
|
|
Texture texture = mat.mainTexture;
|
|
fxMat.mainTexture = texture;
|
|
fxMat.mainTextureOffset = mat.mainTextureOffset;
|
|
fxMat.mainTextureScale = mat.mainTextureScale;
|
|
}
|
|
if (useAlphaTest) {
|
|
fxMat.SetFloat (UNIFORM_CUTOFF, alphaCutOff);
|
|
fxMat.EnableKeyword (SKW_ALPHACLIP);
|
|
} else {
|
|
fxMat.DisableKeyword (SKW_ALPHACLIP);
|
|
}
|
|
fxMat.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
}
|
|
|
|
// See-through
|
|
fxMat = rms [k].fxMatSeeThrough [l];
|
|
if (fxMat != null) {
|
|
fxMat.SetFloat ("_SeeThrough", seeThroughIntensity);
|
|
fxMat.SetColor ("_SeeThroughTintColor", seeThroughTintColor);
|
|
if (hasTexture) {
|
|
Texture texture = mat.mainTexture;
|
|
fxMat.mainTexture = texture;
|
|
fxMat.mainTextureOffset = mat.mainTextureOffset;
|
|
fxMat.mainTextureScale = mat.mainTextureScale;
|
|
}
|
|
if (useAlphaTest) {
|
|
fxMat.SetFloat (UNIFORM_CUTOFF, alphaCutOff);
|
|
fxMat.EnableKeyword (SKW_ALPHACLIP);
|
|
} else {
|
|
fxMat.DisableKeyword (SKW_ALPHACLIP);
|
|
}
|
|
}
|
|
|
|
// Overlay
|
|
fxMat = rms [k].fxMatOverlay [l];
|
|
if (fxMat != null) {
|
|
if (hasTexture) {
|
|
Texture texture = mat.mainTexture;
|
|
fxMat.mainTexture = texture;
|
|
fxMat.mainTextureOffset = mat.mainTextureOffset;
|
|
fxMat.mainTextureScale = mat.mainTextureScale;
|
|
}
|
|
if (mat.HasProperty ("_Color")) {
|
|
fxMat.SetColor ("_OverlayBackColor", mat.GetColor ("_Color"));
|
|
}
|
|
if (useAlphaTest) {
|
|
fxMat.SetFloat (UNIFORM_CUTOFF, alphaCutOff);
|
|
fxMat.EnableKeyword (SKW_ALPHACLIP);
|
|
} else {
|
|
fxMat.DisableKeyword (SKW_ALPHACLIP);
|
|
}
|
|
}
|
|
|
|
// Overlay
|
|
fxMat = rms [k].fxMatInnerGlow [l];
|
|
if (fxMat != null) {
|
|
if (hasTexture) {
|
|
Texture texture = mat.mainTexture;
|
|
fxMat.mainTexture = texture;
|
|
fxMat.mainTextureOffset = mat.mainTextureOffset;
|
|
fxMat.mainTextureScale = mat.mainTextureScale;
|
|
}
|
|
fxMat.SetFloat ("_Width", innerGlowWidth);
|
|
fxMat.SetInt ("_InnerGlowZTest", innerGlowAlwaysOnTop ? (int)UnityEngine.Rendering.CompareFunction.Always : (int)UnityEngine.Rendering.CompareFunction.LessEqual);
|
|
if (useAlphaTest) {
|
|
fxMat.SetFloat (UNIFORM_CUTOFF, alphaCutOff);
|
|
fxMat.EnableKeyword (SKW_ALPHACLIP);
|
|
} else {
|
|
fxMat.DisableKeyword (SKW_ALPHACLIP);
|
|
}
|
|
}
|
|
|
|
// Solid Color for smooth glow
|
|
fxMat = rms [k].fxMatSolidColor [l];
|
|
if (fxMat != null) {
|
|
fxMat.color = glowHQColor;
|
|
fxMat.SetInt ("_Cull", cullBackFaces ? (int)UnityEngine.Rendering.CullMode.Back : (int)UnityEngine.Rendering.CullMode.Off);
|
|
if (hasTexture) {
|
|
Texture texture = mat.mainTexture;
|
|
fxMat.mainTexture = texture;
|
|
fxMat.mainTextureOffset = mat.mainTextureOffset;
|
|
fxMat.mainTextureScale = mat.mainTextureScale;
|
|
}
|
|
if (useAlphaTest) {
|
|
fxMat.SetFloat (UNIFORM_CUTOFF, alphaCutOff);
|
|
fxMat.EnableKeyword (SKW_ALPHACLIP);
|
|
} else {
|
|
fxMat.DisableKeyword (SKW_ALPHACLIP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BuildQuad ()
|
|
{
|
|
quadMesh = new Mesh ();
|
|
|
|
// Setup vertices
|
|
Vector3[] newVertices = new Vector3[4];
|
|
float halfHeight = 0.5f;
|
|
float halfWidth = 0.5f;
|
|
newVertices [0] = new Vector3 (-halfWidth, -halfHeight, 0);
|
|
newVertices [1] = new Vector3 (-halfWidth, halfHeight, 0);
|
|
newVertices [2] = new Vector3 (halfWidth, -halfHeight, 0);
|
|
newVertices [3] = new Vector3 (halfWidth, halfHeight, 0);
|
|
|
|
// Setup UVs
|
|
Vector2[] newUVs = new Vector2[newVertices.Length];
|
|
newUVs [0] = new Vector2 (0, 0);
|
|
newUVs [1] = new Vector2 (0, 1);
|
|
newUVs [2] = new Vector2 (1, 0);
|
|
newUVs [3] = new Vector2 (1, 1);
|
|
|
|
// Setup triangles
|
|
int[] newTriangles = new int[] { 0, 1, 2, 3, 2, 1 };
|
|
|
|
// Setup normals
|
|
Vector3[] newNormals = new Vector3[newVertices.Length];
|
|
for (int i = 0; i < newNormals.Length; i++) {
|
|
newNormals [i] = Vector3.forward;
|
|
}
|
|
|
|
// Create quad
|
|
quadMesh.vertices = newVertices;
|
|
quadMesh.uv = newUVs;
|
|
quadMesh.triangles = newTriangles;
|
|
quadMesh.normals = newNormals;
|
|
|
|
quadMesh.RecalculateBounds ();
|
|
}
|
|
|
|
#region Normals handling
|
|
|
|
static Vector3[] newNormals;
|
|
static int[] matches;
|
|
static Dictionary<Vector3, int> vv;
|
|
|
|
void AverageNormals (int objIndex)
|
|
{
|
|
Mesh mesh = rms [objIndex].mesh;
|
|
if (!mesh.isReadable) return;
|
|
Vector3[] normals = mesh.normals;
|
|
if (normals == null)
|
|
return;
|
|
Vector3[] vertices = mesh.vertices;
|
|
int vertexCount = vertices.Length;
|
|
if (newNormals == null || newNormals.Length < vertexCount) {
|
|
newNormals = new Vector3[vertexCount];
|
|
} else {
|
|
Vector3 zero = Vector3.zero;
|
|
for (int k = 0; k < vertexCount; k++) {
|
|
newNormals [k] = zero;
|
|
}
|
|
}
|
|
if (matches == null || matches.Length < vertexCount) {
|
|
matches = new int[vertexCount];
|
|
}
|
|
if (vv == null) {
|
|
vv = new Dictionary<Vector3, int> (vertexCount);
|
|
} else {
|
|
vv.Clear ();
|
|
}
|
|
// Locate overlapping vertices
|
|
for (int k = 0; k < vertexCount; k++) {
|
|
int i;
|
|
if (!vv.TryGetValue (vertices [k], out i)) {
|
|
vv[vertices [k]] = i = k;
|
|
}
|
|
matches [k] = i;
|
|
}
|
|
// Average normals
|
|
for (int k = 0; k < vertexCount; k++) {
|
|
int match = matches [k];
|
|
newNormals [match] += normals [k];
|
|
}
|
|
for (int k = 0; k < vertexCount; k++) {
|
|
int match = matches [k];
|
|
normals [k] = newNormals[match].normalized;
|
|
}
|
|
// Reassign normals
|
|
Mesh newMesh = Instantiate<Mesh> (mesh);
|
|
newMesh.normals = normals;
|
|
rms [objIndex].mesh = newMesh;
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|
|
|
|
|