using UnityEditor; using UnityEngine; using System; using System.IO; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEditor.SceneManagement; using UnityEngine.SceneManagement; [CustomEditor(typeof(BakerySkyLight))] [CanEditMultipleObjects] public class ftSkyLightInspector : UnityEditor.Editor { public static Quaternion QuaternionFromMatrix(Matrix4x4 m) { Quaternion q = new Quaternion(); q.w = Mathf.Sqrt( Mathf.Max( 0, 1 + m[0,0] + m[1,1] + m[2,2] ) ) / 2; q.x = Mathf.Sqrt( Mathf.Max( 0, 1 + m[0,0] - m[1,1] - m[2,2] ) ) / 2; q.y = Mathf.Sqrt( Mathf.Max( 0, 1 - m[0,0] + m[1,1] - m[2,2] ) ) / 2; q.z = Mathf.Sqrt( Mathf.Max( 0, 1 - m[0,0] - m[1,1] + m[2,2] ) ) / 2; q.x *= Mathf.Sign( q.x * ( m[2,1] - m[1,2] ) ); q.y *= Mathf.Sign( q.y * ( m[0,2] - m[2,0] ) ); q.z *= Mathf.Sign( q.z * ( m[1,0] - m[0,1] ) ); return q; } SerializedProperty ftraceLightColor; SerializedProperty ftraceLightIntensity; SerializedProperty ftraceLightTexture; SerializedProperty ftraceLightSamples; SerializedProperty ftraceLightHemi; SerializedProperty ftraceLightCorrectRot; SerializedProperty ftraceLightBitmask; SerializedProperty ftraceLightBakeToIndirect; SerializedProperty ftraceLightIndirectIntensity; SerializedProperty ftraceTangentSH; int texCached = -1; void TestPreviewRefreshProperty(ref int cached, int newVal) { if (cached >= 0) { if (cached != newVal) { BakerySkyLight.lightsChanged = 2; } } cached = newVal; } void TestPreviewRefreshProperty(ref int cached, UnityEngine.Object newVal) { if (newVal == null) { TestPreviewRefreshProperty(ref cached, 0); return; } TestPreviewRefreshProperty(ref cached, newVal.GetInstanceID()); } static string ftSkyboxShaderName = "Bakery/Skybox"; ftLightmapsStorage storage; static string[] selStrings = new string[] {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16", "17","18","19","20","21","22","23","24","25","26","27","28","29","30"};//,"31"}; static public string[] directContributionOptions = new string[] {"Direct And Indirect (recommended)", "Indirect only"}; bool showExperimental = false; void OnEnable() { ftraceLightColor = serializedObject.FindProperty("color"); ftraceLightIntensity = serializedObject.FindProperty("intensity"); ftraceLightIndirectIntensity = serializedObject.FindProperty("indirectIntensity"); ftraceLightTexture = serializedObject.FindProperty("cubemap"); ftraceLightSamples = serializedObject.FindProperty("samples"); ftraceLightHemi = serializedObject.FindProperty("hemispherical"); ftraceLightCorrectRot = serializedObject.FindProperty("correctRotation"); ftraceLightBitmask = serializedObject.FindProperty("bitmask"); ftraceLightBakeToIndirect = serializedObject.FindProperty("bakeToIndirect"); ftraceTangentSH = serializedObject.FindProperty("tangentSH"); } public override void OnInspectorGUI() { { serializedObject.Update(); TestPreviewRefreshProperty(ref texCached, ftraceLightTexture.objectReferenceValue); EditorGUILayout.PropertyField(ftraceLightColor, new GUIContent("Color", "Sky color. Multiplies texture color.")); EditorGUILayout.PropertyField(ftraceLightIntensity, new GUIContent("Intensity", "Color multiplier")); EditorGUILayout.PropertyField(ftraceLightTexture, new GUIContent("Sky texture", "Cubemap")); if (ftraceLightTexture.objectReferenceValue != null) { EditorGUILayout.PropertyField(ftraceLightCorrectRot, new GUIContent("Correct rotation", "Enable to have a proper match between GameObject rotation and HDRI rotation. Disabled by default for backwards compatibility.")); var angles = (target as BakerySkyLight).transform.eulerAngles; EditorGUILayout.LabelField("Cubemap angles: " + angles.x + ", " + angles.y + ", " + angles.z); EditorGUILayout.LabelField("Rotate this GameObject to change cubemap angles."); EditorGUILayout.Space(); } EditorGUILayout.PropertyField(ftraceLightSamples, new GUIContent("Samples", "The amount of rays tested for this light. Rays are emitted hemispherically.")); EditorGUILayout.PropertyField(ftraceLightHemi, new GUIContent("Hemispherical", "Only emit light from upper hemisphere")); //ftraceLightBitmask.intValue = EditorGUILayout.MaskField(new GUIContent("Bitmask", "Lights only affect renderers with overlapping bits"), ftraceLightBitmask.intValue, selStrings); int prevVal = ftraceLightBitmask.intValue; int newVal = EditorGUILayout.MaskField(new GUIContent("Bitmask", "Lights only affect renderers with overlapping bits"), ftraceLightBitmask.intValue, selStrings); if (prevVal != newVal) ftraceLightBitmask.intValue = newVal; //EditorGUILayout.PropertyField(ftraceLightBakeToIndirect, new GUIContent("Bake to indirect", "Add direct contribution from this light to indirect-only lightmaps")); if (storage == null) storage = ftRenderLightmap.FindRenderSettingsStorage(); var rmode = storage.renderSettingsUserRenderMode; if (rmode != (int)ftRenderLightmap.RenderMode.FullLighting) { ftDirectLightInspector.BakeWhat contrib; if (ftraceLightBakeToIndirect.boolValue) { contrib = ftDirectLightInspector.BakeWhat.DirectAndIndirect; } else { contrib = ftDirectLightInspector.BakeWhat.IndirectOnly; } var prevContrib = contrib; contrib = (ftDirectLightInspector.BakeWhat)EditorGUILayout.Popup("Baked contribution", (int)contrib, directContributionOptions); if (prevContrib != contrib) { if (contrib == ftDirectLightInspector.BakeWhat.IndirectOnly) { ftraceLightBakeToIndirect.boolValue = false; } else { ftraceLightBakeToIndirect.boolValue = true; } } } EditorGUILayout.PropertyField(ftraceLightIndirectIntensity, new GUIContent("Indirect intensity", "Non-physical GI multiplier for this light")); showExperimental = EditorGUILayout.Foldout(showExperimental, "Experimental", EditorStyles.foldout); if (showExperimental) { EditorGUILayout.PropertyField(ftraceTangentSH, new GUIContent("Tangent-space SH", "Only affects single-color skylights. When baking in SH mode, harmonics will be in tangent space. Can be useful for implementing skinned model specular occlusion in custom shaders.")); } serializedObject.ApplyModifiedProperties(); } var skyMat = RenderSettings.skybox; bool match = false; bool skyboxValid = true; string why = ""; if (skyMat != null) { if (skyMat.HasProperty("_Tex") && skyMat.HasProperty("_Exposure") && skyMat.HasProperty("_Tint")) { if (skyMat.GetTexture("_Tex") == ftraceLightTexture.objectReferenceValue) { float exposure = skyMat.GetFloat("_Exposure"); bool exposureSRGB = skyMat.shader.name == "Skybox/Cubemap"; if (exposureSRGB) { exposure = Mathf.Pow(exposure, 2.2f); // can't detect [Gamma] keyword... exposure *= PlayerSettings.colorSpace == ColorSpace.Linear ? 4.59f : 2; // weird unity constant } if (Mathf.Abs(exposure - ftraceLightIntensity.floatValue) < 0.0001f) { if (skyMat.GetColor("_Tint") == ftraceLightColor.colorValue) { bool anglesMatch = true; var angles = (target as BakerySkyLight).transform.eulerAngles; Vector3 matMatrixX = Vector3.right; Vector3 matMatrixY = Vector3.up; Vector3 matMatrixZ = Vector3.forward; float matAngleY = 0; bool hasYAngle = skyMat.HasProperty("_Rotation"); bool hasXZAngles = skyMat.HasProperty("_MatrixRight"); if (hasYAngle) matAngleY = skyMat.GetFloat("_Rotation"); if (hasXZAngles) { matMatrixX = skyMat.GetVector("_MatrixRight"); matMatrixY = skyMat.GetVector("_MatrixUp"); matMatrixZ = skyMat.GetVector("_MatrixForward"); } if (angles.y != 0 && !hasYAngle) { anglesMatch = false; why = "no _Rotation property, but light is rotated"; } else if ((angles.x != 0 || angles.z != 0) && !hasXZAngles) { anglesMatch = false; why = "shader doesn't allow XZ rotation"; } else { var lightQuat = (target as BakerySkyLight).transform.rotation; Quaternion matQuat; if (hasXZAngles) { var mtx = new Matrix4x4(); mtx.SetColumn(0, new Vector4(matMatrixX.x, matMatrixX.y, matMatrixX.z, 0)); mtx.SetColumn(1, new Vector4(matMatrixY.x, matMatrixY.y, matMatrixY.z, 0)); mtx.SetColumn(2, new Vector4(matMatrixZ.x, matMatrixZ.y, matMatrixZ.z, 0)); matQuat = QuaternionFromMatrix(mtx); } else { matQuat = Quaternion.Euler(0, matAngleY, 0); } float diff = Quaternion.Angle(matQuat, lightQuat); //Debug.Log("d " + diff); if (Mathf.Abs(diff) > 0.01f) { anglesMatch = false; why = "angles don't match"; } } if (anglesMatch) match = true; } else { why = "color doesn't match"; } } else { why = "exposure doesn't match"; } } else { why = "texture doesn't match"; } } else { if (!skyMat.HasProperty("_Tex")) why += "_Tex "; if (!skyMat.HasProperty("_Exposure")) why += "_Exposure "; if (!skyMat.HasProperty("_Tint")) why += "_Tint "; why += "not defined"; skyboxValid = false; } } else { why = "no skybox set"; skyboxValid = false; } if (!match) { EditorGUILayout.Space(); EditorGUILayout.LabelField("Skylight doesn't match skybox: " + why); EditorGUILayout.Space(); if (skyboxValid) { if (GUILayout.Button("Match this light to scene skybox")) { ftraceLightTexture.objectReferenceValue = skyMat.GetTexture("_Tex"); float exposure = skyMat.GetFloat("_Exposure"); bool exposureSRGB = skyMat.shader.name == "Skybox/Cubemap"; if (exposureSRGB) { exposure = Mathf.Pow(exposure, 2.2f); // can't detect [Gamma] keyword... exposure *= PlayerSettings.colorSpace == ColorSpace.Linear ? 4.59f : 2; // weird unity constant } ftraceLightIntensity.floatValue = exposure; ftraceLightColor.colorValue = skyMat.GetColor("_Tint"); float matAngle = 0; if (skyMat.HasProperty("_Rotation")) matAngle = skyMat.GetFloat("_Rotation"); var matQuat = Quaternion.Euler(0, matAngle, 0); Undo.RecordObject((target as BakerySkyLight).transform, "Rotate skylight"); (target as BakerySkyLight).transform.rotation = matQuat; serializedObject.ApplyModifiedProperties(); } } if (GUILayout.Button("Match scene skybox to this light")) { if (skyMat != null) { Undo.RecordObject(skyMat, "Change skybox"); } var tform = (target as BakerySkyLight).transform; var angles = tform.eulerAngles; if (angles.x !=0 || angles.z !=0) { if (skyboxValid && !skyMat.HasProperty("_MatrixRight")) skyboxValid = false; // only ftrace skybox can handle xz rotation for now } if (angles.y != 0 && skyboxValid && !skyMat.HasProperty("_Rotation")) skyboxValid = false; // needs _Rotation for Y angle if (!skyboxValid) { var outputPath = ftRenderLightmap.outputPath; skyMat = new Material(Shader.Find(ftSkyboxShaderName)); if (!Directory.Exists("Assets/" + outputPath)) { Directory.CreateDirectory("Assets/" + outputPath); } AssetDatabase.CreateAsset(skyMat, "Assets/" + outputPath + "/" + SceneManager.GetActiveScene().name + "_skybox.asset"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } skyMat.SetTexture("_Tex", ftraceLightTexture.objectReferenceValue as Cubemap); skyMat.SetFloat("_NoTexture", ftraceLightTexture.objectReferenceValue == null ? 1 : 0); float exposure = ftraceLightIntensity.floatValue; bool exposureSRGB = skyMat.shader.name == "Skybox/Cubemap"; if (exposureSRGB) { exposure /= PlayerSettings.colorSpace == ColorSpace.Linear ? 4.59f : 2; // weird unity constant exposure = Mathf.Pow(exposure, 1.0f / 2.2f); // can't detect [Gamma] keyword... } skyMat.SetFloat("_Exposure", exposure); skyMat.SetColor("_Tint", ftraceLightColor.colorValue); if (skyMat.HasProperty("_Rotation")) skyMat.SetFloat("_Rotation", angles.y); if ((target as BakerySkyLight).correctRotation) { // transpose var r = tform.right; var u = tform.up; var f = tform.forward; if (skyMat.HasProperty("_MatrixRight")) skyMat.SetVector("_MatrixRight", new Vector3(r.x, u.x, f.x)); if (skyMat.HasProperty("_MatrixUp")) skyMat.SetVector("_MatrixUp", new Vector3(r.y, u.y, f.y)); if (skyMat.HasProperty("_MatrixForward")) skyMat.SetVector("_MatrixForward", new Vector3(r.z, u.z, f.z)); } else { if (skyMat.HasProperty("_MatrixRight")) skyMat.SetVector("_MatrixRight", tform.right); if (skyMat.HasProperty("_MatrixUp")) skyMat.SetVector("_MatrixUp", tform.up); if (skyMat.HasProperty("_MatrixForward")) skyMat.SetVector("_MatrixForward", tform.forward); } RenderSettings.skybox = skyMat; EditorUtility.SetDirty(skyMat); EditorSceneManager.MarkAllScenesDirty(); } EditorGUILayout.Space(); EditorGUILayout.Space(); } } }