497 lines
21 KiB
C#
497 lines
21 KiB
C#
|
using System.Runtime.CompilerServices;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace UnityEditor.Rendering
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Helper class for drawing shadow cascade with GUI.
|
||
|
/// </summary>
|
||
|
public static class ShadowCascadeGUI
|
||
|
{
|
||
|
private const string kPathToHorizontalGradientTexture = "Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/HorizontalGradient.png";
|
||
|
private const string kPathToUpSnatchTexture = "Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/UpSnatch.png";
|
||
|
private const string kPathToUpSnatchFocusedTexture = "Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/UpSnatchFocused.png";
|
||
|
private const string kPathToDownSnatchTexture = "Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/DownSnatch.png";
|
||
|
private const string kPathTDownSnatchFocusedTexture = "Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/DownSnatchFocused.png";
|
||
|
|
||
|
private const float kSliderbarMargin = 2.0f;
|
||
|
private const float kSliderbarHeight = 28.0f;
|
||
|
|
||
|
//Value that used in LODSliderRange in normal background texture
|
||
|
private const float kLODSliderRangeModifier = 0.78824f;
|
||
|
|
||
|
// Keep in sync with the ones in Debug.hlsl
|
||
|
private static readonly Color[] kCascadeColors =
|
||
|
{
|
||
|
new Color(0.5f, 0.5f, 0.7f, 1.0f),
|
||
|
new Color(0.5f, 0.7f, 0.5f, 1.0f),
|
||
|
new Color(0.7f, 0.7f, 0.5f, 1.0f),
|
||
|
new Color(0.7f, 0.5f, 0.5f, 1.0f),
|
||
|
};
|
||
|
private static readonly Color kDisabledColor = new Color(0.5f, 0.5f, 0.5f, 0.4f); //Works with both personal and pro skin
|
||
|
|
||
|
private static Vector2 s_DragLastMousePosition;
|
||
|
private static readonly int s_CascadeSliderId = "s_CascadeSliderId".GetHashCode();
|
||
|
|
||
|
private static GUIStyle s_HorizontalGradient = null; // Lazy init
|
||
|
private static GUIStyle s_UpSnatch = null; // Lazy init
|
||
|
private static GUIStyle s_DownSnatch = null; // Lazy init
|
||
|
private static readonly GUIStyle s_CascadeSliderBG = "LODSliderRange"; // Using a LODGroup skin
|
||
|
private static readonly GUIStyle s_TextCenteredStyle = new GUIStyle(EditorStyles.whiteMiniLabel)
|
||
|
{
|
||
|
alignment = TextAnchor.MiddleCenter
|
||
|
};
|
||
|
|
||
|
/// <summary>
|
||
|
/// Represents the state of the cascade handle.
|
||
|
/// </summary>
|
||
|
public enum HandleState
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Handle will not be drawn.
|
||
|
/// </summary>
|
||
|
Hidden,
|
||
|
/// <summary>
|
||
|
/// Handle will be disabled.
|
||
|
/// </summary>
|
||
|
Disabled,
|
||
|
/// <summary>
|
||
|
/// Handle will be enabled.
|
||
|
/// </summary>
|
||
|
Enabled,
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Data of single cascade for drawing in GUI.
|
||
|
/// </summary>
|
||
|
public struct Cascade
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Cascade normalized size that ranges from 0 to 1.
|
||
|
/// Sum of all cascade sizes can not exceed 1.
|
||
|
/// </summary>
|
||
|
public float size;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Cascade border size that ranges from 0 to 1.
|
||
|
/// Border represents the width of shadow blend.
|
||
|
/// Where 0 value result in no blend and 1 will blend from cascade beginning.
|
||
|
/// </summary>
|
||
|
public float borderSize;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Current state of cascade handle that will be used for drawing it.
|
||
|
/// </summary>
|
||
|
public HandleState cascadeHandleState;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Current state of border handle that will be used for drawing it.
|
||
|
/// </summary>
|
||
|
public HandleState borderHandleState;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Draw cascades using editor GUI. This also includes handles
|
||
|
/// </summary>
|
||
|
/// <param name="cascades">Array of cascade data.</param>
|
||
|
/// <param name="useMetric">True if numbers should be presented with metric system, otherwise procentage.</param>
|
||
|
/// <param name="baseMetric">The base of the metric system. In most cases it is maximum shadow distance.</param>
|
||
|
public static void DrawCascades(ref Cascade[] cascades, bool useMetric, float baseMetric)
|
||
|
{
|
||
|
// Validate arguments
|
||
|
if (cascades == null || cascades.Length == 0)
|
||
|
{
|
||
|
Debug.LogError($"No cascades passed.");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Validate cascade sizes
|
||
|
float cascadeSizeSum = 0;
|
||
|
for (int i = 0; i < cascades.Length; ++i)
|
||
|
{
|
||
|
cascadeSizeSum += cascades[i].size;
|
||
|
}
|
||
|
if (Mathf.Abs(cascadeSizeSum - 1f) > 0.01f)
|
||
|
{
|
||
|
Debug.LogError($"Cascade total sum of size must be 1.0 (Currently it is {cascadeSizeSum}).");
|
||
|
|
||
|
// Normalize
|
||
|
for (int i = 0; i < cascades.Length; ++i)
|
||
|
{
|
||
|
if (cascadeSizeSum > 0)
|
||
|
cascades[i].size /= cascadeSizeSum;
|
||
|
else
|
||
|
cascades[i].size = (1f / cascades.Length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Validate cascade border sizes
|
||
|
for (int i = 0; i < cascades.Length; ++i)
|
||
|
{
|
||
|
cascades[i].borderSize = Mathf.Clamp01(cascades[i].borderSize);
|
||
|
}
|
||
|
|
||
|
EditorGUILayout.BeginVertical();
|
||
|
|
||
|
// Space for cascade handles
|
||
|
GUILayout.Space(13f);
|
||
|
|
||
|
EditorGUILayout.BeginHorizontal();
|
||
|
|
||
|
// Correctly handle indents
|
||
|
GUILayout.Space(EditorGUI.indentLevel * 15f);
|
||
|
|
||
|
var sliderRect = GUILayoutUtility.GetRect(
|
||
|
GUIContent.none,
|
||
|
s_CascadeSliderBG,
|
||
|
GUILayout.Height(kSliderbarMargin + kSliderbarHeight + kSliderbarMargin),
|
||
|
GUILayout.ExpandWidth(true));
|
||
|
DrawBackgroundBoxGUI(sliderRect, Color.gray);
|
||
|
|
||
|
var formatSymbol = useMetric ? 'm' : '%';
|
||
|
var usableRect = new Rect(sliderRect.x + kSliderbarMargin, sliderRect.y + kSliderbarMargin, sliderRect.width - kSliderbarMargin * 2, sliderRect.height - kSliderbarMargin * 2);
|
||
|
var partitionWidth = 2.0f / EditorGUIUtility.pixelsPerPoint;
|
||
|
var partitionHalfWidth = partitionWidth * 0.5f;
|
||
|
|
||
|
// Calculate pixel perfect cascade widths
|
||
|
float widthForCascades = usableRect.width;
|
||
|
float[] cascadeWidths = new float[cascades.Length];
|
||
|
float sumOfCascadeWidthsWithoutLast = 0;
|
||
|
float startX = 0;
|
||
|
for (int i = 0; i < cascades.Length - 1; ++i)
|
||
|
{
|
||
|
float endX = startX + cascades[i].size * widthForCascades;
|
||
|
|
||
|
float pixelPerfectStartX = Mathf.Round(startX * EditorGUIUtility.pixelsPerPoint) / EditorGUIUtility.pixelsPerPoint;
|
||
|
float pixelPerfectEndX = Mathf.Round(endX * EditorGUIUtility.pixelsPerPoint) / EditorGUIUtility.pixelsPerPoint;
|
||
|
float pixelPerfectCascadeWidth = pixelPerfectEndX - pixelPerfectStartX;
|
||
|
|
||
|
cascadeWidths[i] = pixelPerfectCascadeWidth;
|
||
|
sumOfCascadeWidthsWithoutLast += cascadeWidths[i];
|
||
|
|
||
|
startX = endX;
|
||
|
}
|
||
|
cascadeWidths[cascades.Length - 1] = widthForCascades - sumOfCascadeWidthsWithoutLast;
|
||
|
|
||
|
float currentX = usableRect.x;
|
||
|
for (int i = 0; i < cascades.Length; ++i)
|
||
|
{
|
||
|
ref var cascade = ref cascades[i];
|
||
|
var cascadeWidth = cascadeWidths[i];
|
||
|
|
||
|
bool isLastCascade = (i == cascades.Length - 1);
|
||
|
|
||
|
// Split cascade into cascade without border and border
|
||
|
float borderValue;
|
||
|
float cascadeValue;
|
||
|
float borderWidth;
|
||
|
float cascadeWithoutBorderWidth;
|
||
|
if (cascade.borderHandleState != HandleState.Hidden)
|
||
|
{
|
||
|
borderValue = cascade.size * cascade.borderSize;
|
||
|
cascadeValue = cascade.size - borderValue;
|
||
|
var cascadeWidthWithoutPartition = cascadeWidth;
|
||
|
cascadeWithoutBorderWidth = Mathf.Round(cascadeWidthWithoutPartition * (1 - cascade.borderSize) * EditorGUIUtility.pixelsPerPoint) / EditorGUIUtility.pixelsPerPoint;
|
||
|
borderWidth = cascadeWidth - cascadeWithoutBorderWidth;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
borderValue = 0;
|
||
|
cascadeValue = cascade.size;
|
||
|
borderWidth = 0;
|
||
|
cascadeWithoutBorderWidth = cascadeWidth;
|
||
|
}
|
||
|
|
||
|
// Draw cascade
|
||
|
var cascadeRect = new Rect(currentX, usableRect.y, cascadeWithoutBorderWidth, usableRect.height);
|
||
|
currentX += DrawBoxGUI(cascadeRect, kCascadeColors[i]);
|
||
|
|
||
|
// Draw cascade text
|
||
|
float cascadeValueForText = useMetric ? cascadeValue * baseMetric : cascadeValue * 100;
|
||
|
string cascadeText = $"{i}\n{cascadeValueForText:F1}{formatSymbol}";
|
||
|
DrawLabelGUI(cascadeRect, cascadeText, Color.black);
|
||
|
|
||
|
if (cascade.borderHandleState != HandleState.Hidden)
|
||
|
{
|
||
|
// As we are rounding everything against pixel per point and subtracting from total it might result in fractions for the last cascade border
|
||
|
if (isLastCascade && cascade.borderSize == 0.0)
|
||
|
borderWidth = 0;
|
||
|
|
||
|
// Draw border snatch handle
|
||
|
var borderPartitionHandleRect = new Rect(
|
||
|
currentX - 6 - partitionHalfWidth,
|
||
|
usableRect.y + usableRect.height - 1,
|
||
|
12,
|
||
|
18);
|
||
|
var enabled = cascade.borderHandleState == HandleState.Enabled;
|
||
|
var borderPartitionColor = enabled ? kCascadeColors[i] : kDisabledColor;
|
||
|
var delta = DrawSnatchWithHandle(borderPartitionHandleRect, cascadeWidth, borderPartitionColor, GetUpSnatchStyle(), enabled);
|
||
|
cascade.borderSize = Mathf.Clamp01(cascade.borderSize - delta);
|
||
|
|
||
|
// Draw border partition
|
||
|
DrawBoxGUI(new Rect(currentX - partitionWidth, usableRect.y, partitionWidth, usableRect.height), Color.black);
|
||
|
|
||
|
// Draw border
|
||
|
var borderRect = new Rect(currentX, usableRect.y, borderWidth, usableRect.height);
|
||
|
var gradientLeftColor = kCascadeColors[i];
|
||
|
var gradientRightColor = isLastCascade ? Color.black : kCascadeColors[i + 1];
|
||
|
currentX += DrawGradientBoxGUI(borderRect, gradientLeftColor, gradientRightColor);
|
||
|
|
||
|
// Draw border text
|
||
|
float borderValueForText = useMetric ? borderValue * baseMetric : borderValue * 100;
|
||
|
string borderText;
|
||
|
if (isLastCascade)
|
||
|
{
|
||
|
string fallbackText = (borderWidth < 57) ? "F." : "Fallback";
|
||
|
borderText = $"{i}\u2192{fallbackText}\n{borderValueForText:F1}{formatSymbol}";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
borderText = $"{i}\u2192{i + 1}\n{borderValueForText:F1}{formatSymbol}";
|
||
|
}
|
||
|
DrawLabelGUI(borderRect, borderText, Color.black);
|
||
|
}
|
||
|
|
||
|
if (!isLastCascade) // Don't draw partition for last cascade
|
||
|
{
|
||
|
if (cascade.cascadeHandleState != HandleState.Hidden)
|
||
|
{
|
||
|
// Draw cascade partition snatch handle
|
||
|
var cascadeHandleRect = new Rect(
|
||
|
currentX - 6 - partitionHalfWidth,
|
||
|
usableRect.y - 19 + 1,
|
||
|
12,
|
||
|
18);
|
||
|
var enabled = cascade.cascadeHandleState == HandleState.Enabled;
|
||
|
var cascadePartitionColor = enabled ? kCascadeColors[i + 1] : kDisabledColor;
|
||
|
var delta = DrawSnatchWithHandle(cascadeHandleRect, usableRect.width, cascadePartitionColor, GetDownSnatchStyle(), enabled);
|
||
|
|
||
|
if (delta != 0)
|
||
|
{
|
||
|
ref var nextCascade = ref cascades[i + 1];
|
||
|
|
||
|
// We want to resize only the current cascade and next cascade
|
||
|
// Lets convert this problem to the slider
|
||
|
var sliderMinimum = 0;
|
||
|
var sliderMaximum = cascade.size + nextCascade.size;
|
||
|
var sliderPosition = cascade.size + delta;
|
||
|
|
||
|
// Force minimum cascade size and prevent cascade going out of bounds
|
||
|
var cascadeMinimumSize = 0.001f;
|
||
|
var sliderPositionPixelPerfectClamped = Mathf.Clamp(sliderPosition,
|
||
|
sliderMinimum + cascadeMinimumSize, sliderMaximum - cascadeMinimumSize);
|
||
|
|
||
|
cascade.size = sliderPositionPixelPerfectClamped;
|
||
|
nextCascade.size = sliderMaximum - sliderPositionPixelPerfectClamped;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Draw cascade partition
|
||
|
DrawBoxGUI(new Rect(currentX - partitionWidth, usableRect.y, partitionWidth, usableRect.height), Color.black);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EditorGUILayout.EndHorizontal();
|
||
|
|
||
|
// Space for border handles
|
||
|
GUILayout.Space(15f);
|
||
|
|
||
|
EditorGUILayout.EndVertical();
|
||
|
}
|
||
|
|
||
|
private static float DrawBackgroundBoxGUI(Rect rect, Color color)
|
||
|
{
|
||
|
var cachedColor = GUI.backgroundColor;
|
||
|
GUI.backgroundColor = color;
|
||
|
GUI.Box(rect, GUIContent.none);
|
||
|
GUI.backgroundColor = cachedColor;
|
||
|
return rect.width;
|
||
|
}
|
||
|
|
||
|
private static float DrawGradientBoxGUI(Rect rect, Color leftColor, Color rightColor)
|
||
|
{
|
||
|
if (s_HorizontalGradient == null)
|
||
|
{
|
||
|
var horizontalGradientTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(kPathToHorizontalGradientTexture);
|
||
|
Debug.Assert(horizontalGradientTexture != null);
|
||
|
|
||
|
s_HorizontalGradient = new GUIStyle();
|
||
|
s_HorizontalGradient.normal.background = horizontalGradientTexture;
|
||
|
}
|
||
|
|
||
|
var cachedColor = GUI.backgroundColor;
|
||
|
|
||
|
// Draw right color as background
|
||
|
GUI.backgroundColor = rightColor;
|
||
|
GUI.Box(rect, GUIContent.none, s_CascadeSliderBG);
|
||
|
|
||
|
// Draw left color as gradient overlay
|
||
|
// Tune the color of overlay gradient to reflect color darkening from s_CascadeSliderBG (LODSliderRange) style which use AnimationRowOdd (LightSkin) texture for that
|
||
|
GUI.backgroundColor = RGBMultiplied(kLODSliderRangeModifier, leftColor);
|
||
|
|
||
|
GUI.Box(rect, GUIContent.none, s_HorizontalGradient);
|
||
|
|
||
|
GUI.backgroundColor = cachedColor;
|
||
|
|
||
|
return rect.width;
|
||
|
}
|
||
|
|
||
|
private static float DrawBoxGUI(Rect rect, Color color)
|
||
|
{
|
||
|
var cachedColor = GUI.backgroundColor;
|
||
|
GUI.backgroundColor = color;
|
||
|
GUI.Box(rect, GUIContent.none, s_CascadeSliderBG);
|
||
|
GUI.backgroundColor = cachedColor;
|
||
|
return rect.width;
|
||
|
}
|
||
|
|
||
|
private static float DrawLabelGUI(Rect rect, string text, Color color)
|
||
|
{
|
||
|
var cachedColor = GUI.backgroundColor;
|
||
|
var oldColor = GUI.color;
|
||
|
GUI.color = color;
|
||
|
GUI.Label(rect, text, s_TextCenteredStyle);
|
||
|
GUI.backgroundColor = cachedColor;
|
||
|
GUI.color = oldColor;
|
||
|
return rect.width;
|
||
|
}
|
||
|
|
||
|
private static float DrawSnatchWithHandle(Rect rect, float distance, Color color, GUIStyle snatch, bool enabled = true)
|
||
|
{
|
||
|
// check for user input on any of the partition handles
|
||
|
// this mechanism gets the current event in the queue... make sure that the mouse is over our control before consuming the event
|
||
|
int sliderControlId = GUIUtility.GetControlID(s_CascadeSliderId, FocusType.Keyboard, rect);
|
||
|
Event currentEvent = Event.current;
|
||
|
EventType eventType = currentEvent.GetTypeForControl(sliderControlId);
|
||
|
|
||
|
if (eventType == EventType.Repaint)
|
||
|
{
|
||
|
bool isFocused = GUIUtility.keyboardControl == sliderControlId && enabled;
|
||
|
bool isHovered = rect.Contains(currentEvent.mousePosition) && enabled;
|
||
|
|
||
|
var cachedColor = GUI.backgroundColor;
|
||
|
|
||
|
// Draw focused with white color as we want to keep original one in texture
|
||
|
GUI.backgroundColor = Color.white;
|
||
|
if (isFocused)
|
||
|
snatch.Draw(rect, false, false, false, isFocused);
|
||
|
|
||
|
// Draw on top of the snatch texture
|
||
|
GUI.backgroundColor = color * (isFocused || isHovered ? 1.4f : 1.0f);
|
||
|
snatch.Draw(rect, false, false, false, false);
|
||
|
|
||
|
GUI.backgroundColor = cachedColor;
|
||
|
}
|
||
|
|
||
|
float delta = 0;
|
||
|
|
||
|
if (enabled)
|
||
|
{
|
||
|
EditorGUIUtility.AddCursorRect(rect, MouseCursor.ResizeHorizontal, sliderControlId);
|
||
|
|
||
|
switch (eventType)
|
||
|
{
|
||
|
case EventType.KeyDown:
|
||
|
if (GUIUtility.keyboardControl != sliderControlId)
|
||
|
break;
|
||
|
|
||
|
if (currentEvent.keyCode == KeyCode.RightArrow)
|
||
|
{
|
||
|
delta = 0.01f;
|
||
|
GUI.changed = true;
|
||
|
currentEvent.Use();
|
||
|
}
|
||
|
else if (currentEvent.keyCode == KeyCode.LeftArrow)
|
||
|
{
|
||
|
delta = -0.01f;
|
||
|
GUI.changed = true;
|
||
|
currentEvent.Use();
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case EventType.MouseDown:
|
||
|
if (!rect.Contains(currentEvent.mousePosition))
|
||
|
break;
|
||
|
|
||
|
// We do not consume event on purpose.
|
||
|
// In case there is overlapping snatch, this way the last one will be hot control
|
||
|
|
||
|
GUIUtility.hotControl = sliderControlId;
|
||
|
GUIUtility.keyboardControl = sliderControlId;
|
||
|
|
||
|
s_DragLastMousePosition = currentEvent.mousePosition;
|
||
|
break;
|
||
|
|
||
|
case EventType.MouseUp:
|
||
|
// mouseUp event anywhere should release the hotcontrol (if it belongs to us), drags (if any)
|
||
|
if (GUIUtility.hotControl == sliderControlId)
|
||
|
{
|
||
|
GUIUtility.hotControl = 0;
|
||
|
currentEvent.Use();
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case EventType.MouseDrag:
|
||
|
if (GUIUtility.hotControl != sliderControlId)
|
||
|
break;
|
||
|
|
||
|
delta = (currentEvent.mousePosition - s_DragLastMousePosition).x / (distance);
|
||
|
|
||
|
GUI.changed = true;
|
||
|
|
||
|
s_DragLastMousePosition = currentEvent.mousePosition;
|
||
|
currentEvent.Use();
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return delta;
|
||
|
}
|
||
|
|
||
|
private static GUIStyle GetDownSnatchStyle()
|
||
|
{
|
||
|
if (s_DownSnatch == null)
|
||
|
{
|
||
|
var downSnatch = AssetDatabase.LoadAssetAtPath<Texture2D>(kPathToDownSnatchTexture);
|
||
|
Debug.Assert(downSnatch != null);
|
||
|
|
||
|
var downSnatchFocused = AssetDatabase.LoadAssetAtPath<Texture2D>(kPathTDownSnatchFocusedTexture);
|
||
|
Debug.Assert(downSnatchFocused != null);
|
||
|
|
||
|
|
||
|
s_DownSnatch = new GUIStyle();
|
||
|
s_DownSnatch.normal.background = downSnatch;
|
||
|
s_DownSnatch.hover.background = downSnatch; // We will simulate hover with brighter color
|
||
|
s_DownSnatch.focused.background = downSnatchFocused;
|
||
|
}
|
||
|
|
||
|
return s_DownSnatch;
|
||
|
}
|
||
|
|
||
|
private static GUIStyle GetUpSnatchStyle()
|
||
|
{
|
||
|
if (s_UpSnatch == null)
|
||
|
{
|
||
|
var downSnatch = AssetDatabase.LoadAssetAtPath<Texture2D>(kPathToUpSnatchTexture);
|
||
|
Debug.Assert(downSnatch != null);
|
||
|
|
||
|
var downSnatchFocused = AssetDatabase.LoadAssetAtPath<Texture2D>(kPathToUpSnatchFocusedTexture);
|
||
|
Debug.Assert(downSnatchFocused != null);
|
||
|
|
||
|
s_UpSnatch = new GUIStyle();
|
||
|
s_UpSnatch.normal.background = downSnatch;
|
||
|
s_UpSnatch.hover.background = downSnatch; // We will simulate hover with brighter color
|
||
|
s_UpSnatch.focused.background = downSnatchFocused;
|
||
|
}
|
||
|
|
||
|
return s_UpSnatch;
|
||
|
}
|
||
|
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
static Color RGBMultiplied(float multiplier, Color color)
|
||
|
{
|
||
|
return new Color(color.r * multiplier, color.g * multiplier, color.b * multiplier, color.a);
|
||
|
}
|
||
|
}
|
||
|
}
|