3 using UnityEngine.UIElements;
4 using UnityEditor.UIElements;
7 namespace UnityEditor.Rendering.Experimental.LookDev
9 //[CreateAssetMenu(fileName = "Environment", menuName = "LookDev/Environment", order = 1999)]
10 public class Environment : ScriptableObject
12 //[TODO: check if the shadow/sky split worth the indirection]
13 //Note: multi-edition is not supported as we cannot draw multiple HDRI
17 public Cubemap cubemap;
18 // Setup default position to be on the sun in the default HDRI.
19 // This is important as the defaultHDRI don't call the set brightest spot function on first call.
21 internal float m_Latitude = 60.0f; // [-90..90]
23 internal float m_Longitude = 299.0f; // [0..360[
24 //public float intensity = 1.0f;
25 public Color color = Color.white;
30 set => m_Latitude = ClampLatitude(value);
33 internal static float ClampLatitude(float value) => Mathf.Clamp(value, -90, 90);
35 public float longitude
38 set => m_Longitude = ClampLongitude(value);
41 internal static float ClampLongitude(float value)
49 public static implicit operator UnityEngine.Rendering.Experimental.LookDev.Shadow(Shadow shadow)
52 : new UnityEngine.Rendering.Experimental.LookDev.Shadow()
54 cubemap = shadow.cubemap,
55 sunPosition = new Vector2(shadow.m_Longitude, shadow.m_Latitude),
63 public Cubemap cubemap;
64 public float rotation = 0.0f;
65 public float exposure = 1f;
67 public static implicit operator UnityEngine.Rendering.Experimental.LookDev.Sky(Sky sky)
70 : new UnityEngine.Rendering.Experimental.LookDev.Sky()
72 cubemap = sky.cubemap,
73 longitudeOffset = sky.rotation,
74 exposure = sky.exposure
78 public Sky sky = new Sky();
79 public Shadow shadow = new Shadow();
82 [CustomEditor(typeof(Environment))]
83 class EnvironmentEditor : Editor
85 EnvironmentElement m_EnvironmentElement;
87 public sealed override VisualElement CreateInspectorGUI()
88 => m_EnvironmentElement = new EnvironmentElement(target as Environment);
91 public sealed override void OnInspectorGUI() { }
93 override public Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height)
94 => EnvironmentElement.GetLatLongThumbnailTexture(target as Environment, width);
97 interface IBendable<T>
101 public class EnvironmentElement : VisualElement, IBendable<Environment>
103 internal const int k_SkyThumbnailWidth = 200;
104 internal const int k_SkyThumbnailHeight = 100;
105 const int k_SkadowThumbnailWidth = 60;
106 const int k_SkadowThumbnailHeight = 30;
107 const int k_SkadowThumbnailXPosition = 130;
108 const int k_SkadowThumbnailYPosition = 10;
109 static Material s_cubeToLatlongMaterial;
110 static Material cubeToLatlongMaterial
114 if (s_cubeToLatlongMaterial == null || s_cubeToLatlongMaterial.Equals(null))
116 s_cubeToLatlongMaterial = new Material(Shader.Find("Hidden/LookDev/CubeToLatlong"));
118 return s_cubeToLatlongMaterial;
122 VisualElement environmentParams;
123 Environment environment;
126 ObjectField skyCubemapField;
127 FloatSliderField skyRotationOffset;
128 FloatField skyExposureField;
129 ObjectField shadowCubemapField;
130 FloatSliderField shadowSunLatitudeField;
131 FloatSliderField shadowSunLongitudeField;
132 ColorField shadowColor;
134 Action OnChangeCallback;
136 public Environment target => environment;
138 public EnvironmentElement() => Create(withPreview: true);
139 public EnvironmentElement(bool withPreview, Action OnChangeCallback = null)
141 this.OnChangeCallback = OnChangeCallback;
145 public EnvironmentElement(Environment environment)
147 Create(withPreview: true);
151 void Create(bool withPreview)
155 latlong = new Image();
156 latlong.style.width = k_SkyThumbnailWidth;
157 latlong.style.height = k_SkyThumbnailHeight;
161 environmentParams = GetDefaultInspector();
162 Add(environmentParams);
165 public void Bind(Environment environment)
167 this.environment = environment;
168 if (environment == null || environment.Equals(null))
171 if (latlong != null && !latlong.Equals(null))
172 latlong.image = GetLatLongThumbnailTexture();
173 skyCubemapField.SetValueWithoutNotify(environment.sky.cubemap);
174 skyRotationOffset.SetValueWithoutNotify(environment.sky.rotation);
175 //[TODO: reenable when shadow composition will be finished]
176 //shadowCubemapField.SetValueWithoutNotify(environment.shadow.cubemap);
177 //shadowSunLatitudeField.SetValueWithoutNotify(environment.shadow.latitude);
178 //shadowSunLongitudeField.SetValueWithoutNotify(environment.shadow.longitude);
179 //shadowColor.SetValueWithoutNotify(environment.shadow.color);
182 public void Bind(Environment environment, Image deportedLatlong)
184 latlong = deportedLatlong;
188 public Texture2D GetLatLongThumbnailTexture()
189 => GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
191 public static Texture2D GetLatLongThumbnailTexture(Environment environment, int width)
193 int height = width >> 1;
194 RenderTexture oldActive = RenderTexture.active;
195 RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
196 RenderTexture.active = temporaryRT;
197 cubeToLatlongMaterial.SetTexture("_MainTex", environment.sky.cubemap);
198 cubeToLatlongMaterial.SetVector("_WindowParams",
201 -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)
203 1f)); //Pixel per Point
204 cubeToLatlongMaterial.SetVector("_CubeToLatLongParams",
206 Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
210 cubeToLatlongMaterial.SetPass(0);
211 GL.LoadPixelMatrix(0, width, height, 0);
212 GL.Clear(true, true, Color.black);
213 Rect skyRect = new Rect(0, 0, width, height);
214 Renderer.DrawFullScreenQuad(skyRect);
216 if (environment.shadow.cubemap != null)
218 cubeToLatlongMaterial.SetTexture("_MainTex", environment.shadow.cubemap);
219 cubeToLatlongMaterial.SetVector("_WindowParams",
222 -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)
224 1f)); //Pixel per Point
225 cubeToLatlongMaterial.SetVector("_CubeToLatLongParams",
227 Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
231 cubeToLatlongMaterial.SetPass(0);
232 int shadowWidth = (int)(width * (k_SkadowThumbnailWidth / (float)k_SkyThumbnailWidth));
233 int shadowXPosition = (int)(width * (k_SkadowThumbnailXPosition / (float)k_SkyThumbnailWidth));
234 int shadowYPosition = (int)(width * (k_SkadowThumbnailYPosition / (float)k_SkyThumbnailWidth));
235 Rect shadowRect = new Rect(
240 Renderer.DrawFullScreenQuad(shadowRect);
243 Texture2D result = new Texture2D(width, height, TextureFormat.ARGB32, false);
244 result.ReadPixels(new Rect(0, 0, width, height), 0, 0, false);
246 RenderTexture.active = oldActive;
247 UnityEngine.Object.DestroyImmediate(temporaryRT);
251 public VisualElement GetDefaultInspector()
253 VisualElement inspector = new VisualElement() { name = "Inspector" };
254 Foldout skyFoldout = new Foldout()
258 skyCubemapField = new ObjectField("Sky with Sun");
259 skyCubemapField.allowSceneObjects = false;
260 skyCubemapField.objectType = typeof(Cubemap);
261 skyCubemapField.RegisterValueChangedCallback(evt
262 => RegisterChange(ref environment.sky.cubemap, evt.newValue as Cubemap, updatePreview: true));
263 skyFoldout.Add(skyCubemapField);
265 skyRotationOffset = new FloatSliderField("Rotation", 0f, 360f, 5);
266 skyRotationOffset.RegisterValueChangedCallback(evt
267 => RegisterChange(ref environment.sky.rotation, evt.newValue, updatePreview: true));
268 skyFoldout.Add(skyRotationOffset);
270 skyExposureField = new FloatField("Exposure");
271 skyExposureField.RegisterValueChangedCallback(evt
272 => RegisterChange(ref environment.sky.exposure, evt.newValue));
273 skyFoldout.Add(skyExposureField);
274 var style = skyFoldout.Q<Toggle>().style;
275 style.marginLeft = 3;
276 style.unityFontStyleAndWeight = FontStyle.Bold;
277 inspector.Add(skyFoldout);
279 //[TODO: reenable when shadow composition will be finished]
280 //Foldout shadowFoldout = new Foldout()
284 //shadowCubemapField = new ObjectField("Sky w/o Sun");
285 //shadowCubemapField.allowSceneObjects = false;
286 //shadowCubemapField.objectType = typeof(Cubemap);
287 //shadowCubemapField.RegisterValueChangedCallback(evt
288 // => RegisterChange(ref environment.shadow.cubemap, evt.newValue as Cubemap, updatePreview: true));
289 //shadowFoldout.Add(shadowCubemapField);
291 //shadowColor = new ColorField("Color");
292 //shadowColor.RegisterValueChangedCallback(evt
293 // => RegisterChange(ref environment.shadow.color, evt.newValue));
294 //shadowFoldout.Add(shadowColor);
296 //shadowSunLatitudeField = new FloatSliderField("Sun Latitude", -90f, 90f, 5);
297 //shadowSunLatitudeField.RegisterValueChangedCallback(evt
298 // => RegisterChange(ref environment.shadow.m_Latitude, Environment.Shadow.ClampLatitude(evt.newValue), shadowSunLatitudeField));
299 //shadowFoldout.Add(shadowSunLatitudeField);
301 //shadowSunLongitudeField = new FloatSliderField("Sun Longitude", 0f, 359.999f, 5);
302 //shadowSunLongitudeField.RegisterValueChangedCallback(evt
303 // => RegisterChange(ref environment.shadow.m_Longitude, Environment.Shadow.ClampLongitude(evt.newValue), shadowSunLongitudeField));
304 //shadowFoldout.Add(shadowSunLongitudeField);
306 //Button sunToBrightess = new Button(()
307 // => { /* [TODO] */ })
309 // text = "Sun position to brightest"
311 //shadowFoldout.Add(sunToBrightess);
313 //style = shadowFoldout.Q<Toggle>().style;
314 //style.marginLeft = 3;
315 //style.unityFontStyleAndWeight = FontStyle.Bold;
316 //inspector.Add(shadowFoldout);
321 void RegisterChange<TValueType>(ref TValueType reflectedVariable, TValueType newValue, BaseField<TValueType> resyncField = null, bool updatePreview = false)
323 if (environment == null || environment.Equals(null))
325 reflectedVariable = newValue;
326 resyncField?.SetValueWithoutNotify(newValue);
327 if (updatePreview && latlong != null && !latlong.Equals(null))
328 latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
329 EditorUtility.SetDirty(environment);
330 OnChangeCallback?.Invoke();
333 void RegisterChange(ref float reflectedVariable, float newValue, FloatSliderField resyncField = null, bool updatePreview = false)
335 if (environment == null || environment.Equals(null))
337 reflectedVariable = newValue;
338 resyncField?.SetValueWithoutNotify(newValue);
339 if (updatePreview && latlong != null && !latlong.Equals(null))
340 latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth);
341 EditorUtility.SetDirty(environment);
342 OnChangeCallback?.Invoke();
345 class FloatSliderField : VisualElement, INotifyValueChanged<float>
349 readonly int maxCharLength;
356 if (value == slider.value)
359 float trunkedValue = TrunkValue(value);
360 if (trunkedValue == slider.value)
365 using (ChangeEvent<float> evt = ChangeEvent<float>.GetPooled(slider.value, trunkedValue))
368 SetValueWithoutNotify_Internal(trunkedValue);
374 SetValueWithoutNotify_Internal(trunkedValue);
379 public FloatSliderField(string label, float start, float end, int maxCharLength = -1)
381 this.maxCharLength = maxCharLength;
382 Add(slider = new Slider(label, start, end));
383 slider.Add(endField = new FloatField(maxCharLength));
384 slider.RegisterValueChangedCallback(evt
385 => endField.SetValueWithoutNotify(TrunkValue(evt.newValue)));
386 endField.RegisterValueChangedCallback(evt
387 => slider.SetValueWithoutNotify(TrunkValue(evt.newValue)));
388 endField.style.marginRight = 0;
391 public void SetValueWithoutNotify(float newValue)
392 => SetValueWithoutNotify_Internal(TrunkValue(newValue));
394 void SetValueWithoutNotify_Internal(float newTrunkedValue)
396 //Note: SetValueWithoutNotify do not change the cursor position
397 // Passing by slider will cause a loop but this loop will be break
398 //as new value match the legacy one
399 //slider.SetValueWithoutNotify(newTrunkedValue);
400 slider.value = newTrunkedValue;
401 endField.SetValueWithoutNotify(newTrunkedValue);
404 float TrunkValue(float value)
406 if (maxCharLength < 0)
409 int integerRounded = (int)Math.Round(value);
410 int integerLength = integerRounded.ToString().Length;
411 if (integerLength + 1 >= maxCharLength)
412 return integerRounded;
414 int signChar = value < 0f ? 1 : 0;
415 int integerTrunkedLength = (int)Math.Truncate(value).ToString().Length;
416 return (float)Math.Round(value, maxCharLength - integerLength - 1 + signChar);