123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- using UnityEngine;
- using System;
- using UnityEngine.UIElements;
- using UnityEditor.UIElements;
- namespace UnityEditor.Rendering.LookDev
- {
- /// <summary>
- /// Lighting environment used in LookDev
- /// </summary>
- public class Environment : ScriptableObject
- {
- [SerializeField]
- string m_CubemapGUID;
- Cubemap m_Cubemap;
- internal bool isCubemapOnly { get; private set; } = false;
- /// <summary>
- /// Offset on the longitude. Affect both sky and sun position in Shadow part
- /// </summary>
- public float rotation = 0.0f;
- /// <summary>
- /// Exposure to use with this Sky
- /// </summary>
- public float exposure = 0f;
- // Setup default position to be on the sun in the default HDRI.
- // This is important as the defaultHDRI don't call the set brightest spot function on first call.
- [SerializeField]
- float m_Latitude = 60.0f; // [-90..90]
- [SerializeField]
- float m_Longitude = 299.0f; // [0..360[
- /// <summary>
- /// The shading tint to used when computing shadow from sun
- /// </summary>
- public Color shadowColor = new Color(0.7f, 0.7f, 0.7f);
- /// <summary>
- /// The cubemap used for this part of the lighting environment
- /// </summary>
- public Cubemap cubemap
- {
- get
- {
- if (m_Cubemap == null || m_Cubemap.Equals(null))
- LoadCubemap();
- return m_Cubemap;
- }
- set
- {
- m_Cubemap = value;
- m_CubemapGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_Cubemap));
- }
- }
- /// <summary>
- /// The Latitude position of the sun casting shadows
- /// </summary>
- public float sunLatitude
- {
- get => m_Latitude;
- set => m_Latitude = ClampLatitude(value);
- }
- /// <summary>
- /// The Longitude position of the sun casting shadows
- /// </summary>
- public float sunLongitude
- {
- get => m_Longitude;
- set => m_Longitude = ClampLongitude(value);
- }
- internal static float ClampLatitude(float value) => Mathf.Clamp(value, -90, 90);
-
- internal static float ClampLongitude(float value)
- {
- value = value % 360f;
- if (value < 0.0)
- value += 360f;
- return value;
- }
- internal void UpdateSunPosition(Light sun)
- => sun.transform.rotation = Quaternion.Euler(sunLatitude, rotation + sunLongitude, 0f);
-
- /// <summary>
- /// Compute sun position to be brightest spot of the sky
- /// </summary>
- public void ResetToBrightestSpot()
- => EnvironmentElement.ResetToBrightestSpot(this);
- void LoadCubemap()
- {
- m_Cubemap = null;
- GUID storedGUID;
- GUID.TryParse(m_CubemapGUID, out storedGUID);
- if (!storedGUID.Empty())
- {
- string path = AssetDatabase.GUIDToAssetPath(m_CubemapGUID);
- m_Cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path);
- }
- }
- internal void CopyTo(Environment other)
- {
- other.cubemap = cubemap;
- other.exposure = exposure;
- other.rotation = rotation;
- other.sunLatitude = sunLatitude;
- other.sunLongitude = sunLongitude;
- other.shadowColor = shadowColor;
- other.name = name + " (copy)";
- }
- /// <summary>
- /// Implicit conversion operator to runtime version of sky datas
- /// </summary>
- /// <param name="sky">Editor version of the datas</param>
- public UnityEngine.Rendering.LookDev.Sky sky
- => new UnityEngine.Rendering.LookDev.Sky()
- {
- cubemap = cubemap,
- longitudeOffset = rotation,
- exposure = exposure
- };
- internal static Environment GetTemporaryEnvironmentForCubemap(Cubemap cubemap)
- {
- Environment result = ScriptableObject.CreateInstance<Environment>();
- result.cubemap = cubemap;
- result.isCubemapOnly = true;
- return result;
- }
- internal bool HasCubemapAssetChanged(Cubemap cubemap)
- {
- if (cubemap == null)
- return !String.IsNullOrEmpty(m_CubemapGUID);
- return m_CubemapGUID != AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(cubemap));
- }
- }
- [CustomEditor(typeof(Environment))]
- class EnvironmentEditor : Editor
- {
- //display nothing
- public sealed override VisualElement CreateInspectorGUI() => null;
- // Don't use ImGUI
- public sealed override void OnInspectorGUI() { }
- //but make preview in Project window
- override public Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height)
- => EnvironmentElement.GetLatLongThumbnailTexture(target as Environment, width);
- }
- interface IBendable<T>
- {
- void Bind(T data);
- }
- class EnvironmentElement : VisualElement, IBendable<Environment>
- {
- internal const int k_SkyThumbnailWidth = 200;
- internal const int k_SkyThumbnailHeight = 100;
- static Material s_cubeToLatlongMaterial;
- static Material cubeToLatlongMaterial
- {
- get
- {
- if (s_cubeToLatlongMaterial == null || s_cubeToLatlongMaterial.Equals(null))
- {
- s_cubeToLatlongMaterial = new Material(Shader.Find("Hidden/LookDev/CubeToLatlong"));
- }
- return s_cubeToLatlongMaterial;
- }
- }
- VisualElement environmentParams;
- Environment environment;
- Image latlong;
- ObjectField skyCubemapField;
- FloatField skyRotationOffset;
- FloatField skyExposureField;
- Vector2Field sunPosition;
- ColorField shadowColor;
- TextField environmentName;
- Action OnChangeCallback;
- public Environment target => environment;
- public EnvironmentElement() => Create(withPreview: true);
- public EnvironmentElement(bool withPreview, Action OnChangeCallback = null)
- {
- this.OnChangeCallback = OnChangeCallback;
- Create(withPreview);
- }
- public EnvironmentElement(Environment environment)
- {
- Create(withPreview: true);
- Bind(environment);
- }
- void Create(bool withPreview)
- {
- if (withPreview)
- {
- latlong = new Image();
- latlong.style.width = k_SkyThumbnailWidth;
- latlong.style.height = k_SkyThumbnailHeight;
- Add(latlong);
- }
- environmentParams = GetDefaultInspector();
- Add(environmentParams);
- }
- public void Bind(Environment environment)
- {
- this.environment = environment;
- if (environment == null || environment.Equals(null))
- return;
- if (latlong != null && !latlong.Equals(null))
- latlong.image = GetLatLongThumbnailTexture();
- skyCubemapField.SetValueWithoutNotify(environment.cubemap);
- skyRotationOffset.SetValueWithoutNotify(environment.rotation);
- skyExposureField.SetValueWithoutNotify(environment.exposure);
- sunPosition.SetValueWithoutNotify(new Vector2(environment.sunLongitude, environment.sunLatitude));
- shadowColor.SetValueWithoutNotify(environment.shadowColor);
- environmentName.SetValueWithoutNotify(environment.name);
- }
- public void Bind(Environment environment, Image deportedLatlong)
- {
- latlong = deportedLatlong;
- Bind(environment);
- }
- static public Vector2 PositionToLatLong(Vector2 position)
- {
- Vector2 result = new Vector2();
- result.x = position.y * Mathf.PI * 0.5f * Mathf.Rad2Deg;
- result.y = (position.x * 0.5f + 0.5f) * 2f * Mathf.PI * Mathf.Rad2Deg;
- if (result.x < -90.0f) result.x = -90f;
- if (result.x > 90.0f) result.x = 90f;
- return result;
- }
- public static void ResetToBrightestSpot(Environment environment)
- {
- cubeToLatlongMaterial.SetTexture("_MainTex", environment.cubemap);
- cubeToLatlongMaterial.SetVector("_WindowParams", new Vector4(10000, -1000.0f, 2, 0.0f)); // Neutral value to not clip
- cubeToLatlongMaterial.SetVector("_CubeToLatLongParams", new Vector4(Mathf.Deg2Rad * environment.rotation, 0.5f, 1.0f, 3.0f)); // We use LOD 3 to take a region rather than a single pixel in the map
- cubeToLatlongMaterial.SetPass(0);
- int width = k_SkyThumbnailWidth;
- int height = width >> 1;
- RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
- Texture2D brightestPointTexture = new Texture2D(width, height, TextureFormat.RGBAHalf, false);
- // Convert cubemap to a 2D LatLong to read on CPU
- Graphics.Blit(environment.cubemap, temporaryRT, cubeToLatlongMaterial);
- brightestPointTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
- brightestPointTexture.Apply();
- // CPU read back
- // From Doc: The returned array is a flattened 2D array, where pixels are laid out left to right, bottom to top (i.e. row after row)
- Color[] color = brightestPointTexture.GetPixels();
- RenderTexture.active = null;
- temporaryRT.Release();
- float maxLuminance = 0.0f;
- int maxIndex = 0;
- for (int index = height * width - 1; index >= 0; --index)
- {
- Color pixel = color[index];
- float luminance = pixel.r * 0.2126729f + pixel.g * 0.7151522f + pixel.b * 0.0721750f;
- if (maxLuminance < luminance)
- {
- maxLuminance = luminance;
- maxIndex = index;
- }
- }
- Vector2 sunPosition = PositionToLatLong(new Vector2(((maxIndex % width) / (float)(width - 1)) * 2f - 1f, ((maxIndex / width) / (float)(height - 1)) * 2f - 1f));
- environment.sunLatitude = sunPosition.x;
- environment.sunLongitude = sunPosition.y - environment.rotation;
- }
- public Texture2D GetLatLongThumbnailTexture()
- => GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
- public static Texture2D GetLatLongThumbnailTexture(Environment environment, int width)
- {
- int height = width >> 1;
- RenderTexture oldActive = RenderTexture.active;
- RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
- RenderTexture.active = temporaryRT;
- cubeToLatlongMaterial.SetTexture("_MainTex", environment.cubemap);
- cubeToLatlongMaterial.SetVector("_WindowParams",
- new Vector4(
- height, //height
- -1000f, //y position, -1000f to be sure to not have clipping issue (we should not clip normally but don't want to create a new shader)
- 2f, //margin value
- 1f)); //Pixel per Point
- cubeToLatlongMaterial.SetVector("_CubeToLatLongParams",
- new Vector4(
- Mathf.Deg2Rad * environment.rotation, //rotation of the environment in radian
- 1f, //alpha
- 1f, //intensity
- 0f)); //LOD
- cubeToLatlongMaterial.SetPass(0);
- GL.LoadPixelMatrix(0, width, height, 0);
- GL.Clear(true, true, Color.black);
- Rect skyRect = new Rect(0, 0, width, height);
- Renderer.DrawFullScreenQuad(skyRect);
- Texture2D result = new Texture2D(width, height, TextureFormat.ARGB32, false);
- result.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
- result.Apply(false);
- RenderTexture.active = oldActive;
- UnityEngine.Object.DestroyImmediate(temporaryRT);
- return result;
- }
- public VisualElement GetDefaultInspector()
- {
- VisualElement inspector = new VisualElement() { name = "inspector" };
- VisualElement header = new VisualElement() { name = "inspector-header" };
- header.Add(new Image()
- {
- image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "Environment", forceLowRes: true)
- });
- environmentName = new TextField();
- environmentName.isDelayed = true;
- environmentName.RegisterValueChangedCallback(evt =>
- {
- string path = AssetDatabase.GetAssetPath(environment);
- environment.name = evt.newValue;
- AssetDatabase.SetLabels(environment, new string[] { evt.newValue });
- EditorUtility.SetDirty(environment);
- AssetDatabase.ImportAsset(path);
- environmentName.name = environment.name;
- });
- header.Add(environmentName);
- inspector.Add(header);
- Foldout foldout = new Foldout()
- {
- text = "Environment Settings"
- };
- skyCubemapField = new ObjectField("Sky with Sun")
- {
- tooltip = "A cubemap that will be used as the sky."
- };
- skyCubemapField.allowSceneObjects = false;
- skyCubemapField.objectType = typeof(Cubemap);
- skyCubemapField.RegisterValueChangedCallback(evt =>
- {
- var tmp = environment.cubemap;
- RegisterChange(ref tmp, evt.newValue as Cubemap, updatePreview: true, customResync: () => environment.cubemap = tmp);
- });
- foldout.Add(skyCubemapField);
- skyRotationOffset = new FloatField("Rotation")
- {
- tooltip = "Rotation offset on the longitude of the sky."
- };
- skyRotationOffset.RegisterValueChangedCallback(evt
- => RegisterChange(ref environment.rotation, Environment.ClampLongitude(evt.newValue), skyRotationOffset, updatePreview: true));
- foldout.Add(skyRotationOffset);
- skyExposureField = new FloatField("Exposure")
- {
- tooltip = "The exposure to apply with this sky."
- };
- skyExposureField.RegisterValueChangedCallback(evt
- => RegisterChange(ref environment.exposure, evt.newValue));
- foldout.Add(skyExposureField);
- var style = foldout.Q<Toggle>().style;
- style.marginLeft = 3;
- style.unityFontStyleAndWeight = FontStyle.Bold;
- inspector.Add(foldout);
- sunPosition = new Vector2Field("Sun Position")
- {
- tooltip = "The sun position as (Longitude, Latitude)\nThe button compute brightest position in the sky with sun."
- };
- sunPosition.Q("unity-x-input").Q<FloatField>().formatString = "n1";
- sunPosition.Q("unity-y-input").Q<FloatField>().formatString = "n1";
- sunPosition.RegisterValueChangedCallback(evt =>
- {
- var tmpContainer = new Vector2(
- environment.sunLongitude,
- environment.sunLatitude);
- var tmpNewValue = new Vector2(
- Environment.ClampLongitude(evt.newValue.x),
- Environment.ClampLatitude(evt.newValue.y));
- RegisterChange(ref tmpContainer, tmpNewValue, sunPosition, customResync: () =>
- {
- environment.sunLongitude = tmpContainer.x;
- environment.sunLatitude = tmpContainer.y;
- });
- });
- foldout.Add(sunPosition);
- Button sunToBrightess = new Button(() =>
- {
- ResetToBrightestSpot(environment);
- sunPosition.SetValueWithoutNotify(new Vector2(
- Environment.ClampLongitude(environment.sunLongitude),
- Environment.ClampLatitude(environment.sunLatitude)));
- })
- {
- name = "sunToBrightestButton"
- };
- sunToBrightess.Add(new Image()
- {
- image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "SunPosition", forceLowRes: true)
- });
- sunToBrightess.AddToClassList("sun-to-brightest-button");
- var vector2Input = sunPosition.Q(className: "unity-vector2-field__input");
- vector2Input.Remove(sunPosition.Q(className: "unity-composite-field__field-spacer"));
- vector2Input.Add(sunToBrightess);
- shadowColor = new ColorField("Shadow Tint")
- {
- tooltip = "The wanted shadow tint to be used when computing shadow."
- };
- shadowColor.RegisterValueChangedCallback(evt
- => RegisterChange(ref environment.shadowColor, evt.newValue));
- foldout.Add(shadowColor);
- style = foldout.Q<Toggle>().style;
- style.marginLeft = 3;
- style.unityFontStyleAndWeight = FontStyle.Bold;
- inspector.Add(foldout);
- return inspector;
- }
- void RegisterChange<TValueType>(ref TValueType reflectedVariable, TValueType newValue, BaseField<TValueType> resyncField = null, bool updatePreview = false, Action customResync = null)
- {
- if (environment == null || environment.Equals(null))
- return;
- reflectedVariable = newValue;
- resyncField?.SetValueWithoutNotify(newValue);
- customResync?.Invoke();
- if (updatePreview && latlong != null && !latlong.Equals(null))
- latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
- EditorUtility.SetDirty(environment);
- OnChangeCallback?.Invoke();
- }
- }
- }
|