MaterialPipelineHandler.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using UnityEngine.Rendering;
  2. using System.Collections.Generic;
  3. #if UNITY_EDITOR
  4. using UnityEditor;
  5. #endif
  6. namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets
  7. {
  8. #if UNITY_EDITOR
  9. [InitializeOnLoad]
  10. static class RenderPipelineValidation
  11. {
  12. static RenderPipelineValidation()
  13. {
  14. foreach (var pipelineHandler in GetAllInstances())
  15. pipelineHandler.AutoRefreshPipelineShaders();
  16. }
  17. static List<MaterialPipelineHandler> GetAllInstances()
  18. {
  19. var instances = new List<MaterialPipelineHandler>();
  20. // Find all GUIDs for objects that match the type MaterialPipelineHandler
  21. var guids = AssetDatabase.FindAssets("t:MaterialPipelineHandler");
  22. for (int i = 0; i < guids.Length; i++)
  23. {
  24. string path = AssetDatabase.GUIDToAssetPath(guids[i]);
  25. var asset = AssetDatabase.LoadAssetAtPath<MaterialPipelineHandler>(path);
  26. if (asset != null)
  27. instances.Add(asset);
  28. }
  29. return instances;
  30. }
  31. }
  32. #endif
  33. /// <summary>
  34. /// Serializable class that contains the shader information for a material.
  35. /// </summary>
  36. [System.Serializable]
  37. public class ShaderContainer
  38. {
  39. public Material material;
  40. public bool useSRPShaderName = true;
  41. public string scriptableRenderPipelineShaderName = "Universal Render Pipeline/Lit";
  42. public Shader scriptableRenderPipelineShader;
  43. public bool useBuiltinShaderName = true;
  44. public string builtInPipelineShaderName = "Standard";
  45. public Shader builtInPipelineShader;
  46. }
  47. /// <summary>
  48. /// Scriptable object that allows for setting the shader on a material based on the current render pipeline.
  49. /// Will run automatically OnEnable in the editor to set the shaders on project bootup. Can be refreshed manually with editor button.
  50. /// This exists because while objects render correctly using shadergraph shaders, others do not and using the standard shader resolves various rendering issues.
  51. /// </summary>
  52. [CreateAssetMenu(fileName = "MaterialPipelineHandler", menuName = "XR/MaterialPipelineHandler", order = 0)]
  53. public class MaterialPipelineHandler : ScriptableObject
  54. {
  55. [SerializeField]
  56. [Tooltip("List of materials and their associated shaders.")]
  57. List<ShaderContainer> m_ShaderContainers;
  58. [SerializeField]
  59. [Tooltip("If true, the shaders will be refreshed automatically when the editor opens and when this scriptable object instance is enabled.")]
  60. bool m_AutoRefreshShaders = true;
  61. #if UNITY_EDITOR
  62. void OnEnable()
  63. {
  64. if (Application.isPlaying)
  65. return;
  66. AutoRefreshPipelineShaders();
  67. }
  68. #endif
  69. public void AutoRefreshPipelineShaders()
  70. {
  71. if (m_AutoRefreshShaders)
  72. SetPipelineShaders();
  73. }
  74. /// <summary>
  75. /// Applies the appropriate shader to the materials based on the current render pipeline.
  76. /// </summary>
  77. public void SetPipelineShaders()
  78. {
  79. if (m_ShaderContainers == null)
  80. return;
  81. bool isBuiltinRenderPipeline = GraphicsSettings.currentRenderPipeline == null;
  82. foreach (var info in m_ShaderContainers)
  83. {
  84. if (info.material == null)
  85. continue;
  86. // Find the appropriate shaders based on the toggle
  87. Shader birpShader = info.useBuiltinShaderName ? Shader.Find(info.builtInPipelineShaderName) : info.builtInPipelineShader;
  88. Shader srpShader = info.useSRPShaderName ? Shader.Find(info.scriptableRenderPipelineShaderName) : info.scriptableRenderPipelineShader;
  89. // Determine current shader for comparison
  90. Shader currentShader = info.material.shader;
  91. // Update shader for the current render pipeline only if necessary
  92. if (isBuiltinRenderPipeline && birpShader != null && currentShader != birpShader)
  93. {
  94. info.material.shader = birpShader;
  95. MarkMaterialModified(info.material);
  96. }
  97. else if (!isBuiltinRenderPipeline && srpShader != null && currentShader != srpShader)
  98. {
  99. info.material.shader = srpShader;
  100. MarkMaterialModified(info.material);
  101. }
  102. }
  103. }
  104. static void MarkMaterialModified(Material material)
  105. {
  106. #if UNITY_EDITOR
  107. EditorUtility.SetDirty(material);
  108. #endif
  109. }
  110. }
  111. #if UNITY_EDITOR
  112. /// <summary>
  113. /// Custom property drawer for the shader container class.
  114. /// </summary>
  115. [CustomPropertyDrawer(typeof(ShaderContainer))]
  116. public class ShaderContainerDrawer : PropertyDrawer
  117. {
  118. public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
  119. {
  120. EditorGUI.BeginProperty(position, label, property);
  121. float singleLineHeight = EditorGUIUtility.singleLineHeight;
  122. float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
  123. SerializedProperty materialProp = property.FindPropertyRelative("material");
  124. SerializedProperty useSRPShaderNameProp = property.FindPropertyRelative("useSRPShaderName");
  125. SerializedProperty scriptableShaderNameProp = property.FindPropertyRelative("scriptableRenderPipelineShaderName");
  126. SerializedProperty scriptableShaderProp = property.FindPropertyRelative("scriptableRenderPipelineShader");
  127. SerializedProperty useShaderNameProp = property.FindPropertyRelative("useBuiltinShaderName");
  128. SerializedProperty builtInNameProp = property.FindPropertyRelative("builtInPipelineShaderName");
  129. SerializedProperty builtInShaderProp = property.FindPropertyRelative("builtInPipelineShader");
  130. // Draw Material without the header.
  131. position.height = singleLineHeight;
  132. EditorGUI.PropertyField(position, materialProp);
  133. position.y += singleLineHeight + verticalSpacing;
  134. // SRP Shader header and fields.
  135. EditorGUI.LabelField(position, "Scriptable Render Pipeline Shader", EditorStyles.boldLabel);
  136. position.y += EditorGUIUtility.singleLineHeight + verticalSpacing;
  137. EditorGUI.PropertyField(position, useSRPShaderNameProp);
  138. position.y += singleLineHeight + verticalSpacing;
  139. if (useSRPShaderNameProp.boolValue)
  140. {
  141. EditorGUI.PropertyField(position, scriptableShaderNameProp);
  142. position.y += singleLineHeight + verticalSpacing;
  143. }
  144. else
  145. {
  146. EditorGUI.PropertyField(position, scriptableShaderProp);
  147. position.y += singleLineHeight + verticalSpacing;
  148. }
  149. // Built-in Shader header and fields.
  150. EditorGUI.LabelField(position, "Built-In Render Pipeline Shader", EditorStyles.boldLabel);
  151. position.y += singleLineHeight + verticalSpacing;
  152. EditorGUI.PropertyField(position, useShaderNameProp);
  153. position.y += singleLineHeight + verticalSpacing;
  154. if (useShaderNameProp.boolValue)
  155. {
  156. EditorGUI.PropertyField(position, builtInNameProp);
  157. position.y += singleLineHeight + verticalSpacing;
  158. }
  159. else
  160. {
  161. EditorGUI.PropertyField(position, builtInShaderProp);
  162. position.y += singleLineHeight + verticalSpacing;
  163. }
  164. // Draw a separator line at the end.
  165. position.y += verticalSpacing / 2; // Extra space for the line.
  166. position.height = 1;
  167. EditorGUI.DrawRect(new Rect(position.x, position.y, position.width, 1), Color.gray);
  168. EditorGUI.EndProperty();
  169. }
  170. public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
  171. {
  172. const int baseFieldCount = 4; // The Material field, the two toggles, and one for an optional field.
  173. int extraLineCount = property.FindPropertyRelative("useBuiltinShaderName").boolValue ? 0 : 1;
  174. extraLineCount += property.FindPropertyRelative("useSRPShaderName").boolValue ? 0 : 1;
  175. float singleLineHeight = EditorGUIUtility.singleLineHeight;
  176. float verticalSpacing = EditorGUIUtility.standardVerticalSpacing;
  177. float headerHeight = EditorGUIUtility.singleLineHeight; // No longer need extra height for headers.
  178. // Calculate height for fields and headers
  179. float fieldsHeight = baseFieldCount * singleLineHeight + (baseFieldCount - 1 + extraLineCount) * verticalSpacing;
  180. // Allow space for header, separator line, and a bit of padding before the line.
  181. float headersHeight = 2 * (headerHeight + verticalSpacing);
  182. float separatorSpace = verticalSpacing / 2 + 1; // Additional vertical spacing and line height.
  183. return fieldsHeight + headersHeight + separatorSpace + singleLineHeight * 1.5f;
  184. }
  185. }
  186. /// <summary>
  187. /// Custom editor MaterialPipelineHandler
  188. /// </summary>
  189. [CustomEditor(typeof(MaterialPipelineHandler)), CanEditMultipleObjects]
  190. public class MaterialPipelineHandlerEditor : Editor
  191. {
  192. public override void OnInspectorGUI()
  193. {
  194. base.OnInspectorGUI();
  195. // Draw the "Refresh Shaders" button
  196. if (GUILayout.Button("Refresh Shaders"))
  197. {
  198. foreach (var t in targets)
  199. {
  200. var handler = (MaterialPipelineHandler)t;
  201. handler.SetPipelineShaders();
  202. }
  203. }
  204. }
  205. }
  206. #endif
  207. }