├── package.json.meta ├── Editor.meta ├── MaterialConverter.asmdef.meta ├── Editor ├── MaterialConverter.cs.meta └── MaterialConverter.cs ├── MaterialConverter.asmdef ├── package.json └── README.md /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 24140195aed7c264b975a39336b5b719 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cc8d30a3432026a45abcd0fdb0decc91 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /MaterialConverter.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 24083894d2f32a8418be1395eae07079 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/MaterialConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4a381d62fd13fa4498a1e2ac7c0ea2d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /MaterialConverter.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MaterialConverter", 3 | "references": [], 4 | "includePlatforms": [ 5 | "Editor" 6 | ], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.ltk.materialconverter", 3 | "displayName": "Material Converter", 4 | "version": "0.0.1", 5 | "unity": "2019.1", 6 | "description": "Allows you to convert materials by remapping shader properties.", 7 | "keywords": [ 8 | "SRP", 9 | "HDRP", 10 | "LWRP", 11 | "URP", 12 | "shaders", 13 | "materials", 14 | "unity" 15 | ], 16 | "category": "Unity" 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaterialConverter 2 | ## Description 3 | Allows you to convert materials by remapping shader properties. 4 | This is less automated than the built-in material conversions provided by LWRP and HDRP, but will work with custom shaders. 5 | The tool will convert *all* materials in your project that use the specified source shaders (you can preview this list with the `Material List` button. 6 | 7 | ## Installation 8 | This is set up as a UPM package, so you can add to your project either via Package Manager's `Add package from git URL` [https://github.com/TeckUnity/MaterialConverter.git], or grab the repo and `Add package from disk`. 9 | 10 | ## Instructions 11 | Select one or more materials and/or shaders, and right-click to pull up the context menu (alternately, pull down the `Assets` menu). Select `Convert Material(s)`. 12 | 13 | You will be presented with a list of shaders, and a dropdown for each where you can select a target shader to convert your materials to use. After selecting a shader, you can then specify how to map each property in the source shader to the target shader. If there are any properties in the target shader that match the source shader (name and type), those will automatically map. 14 | 15 | Then simply hit `Convert Materials`. The `Material List` button lets you preview the materials that will be converted. 16 | 17 | ## Examples 18 | Converting from Custom Shader to LWRP/Lit: 19 | 20 | 21 | 22 | Converting from LWRP/Lit to Custom Shader: 23 | 24 | 25 | 26 | ## Todo 27 | * Save property mappings to an asset for future re-use 28 | * Automated conversion on import using the saved mappings 29 | -------------------------------------------------------------------------------- /Editor/MaterialConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System.Linq; 6 | using UnityEditor.AnimatedValues; 7 | using UnityEngine.Events; 8 | 9 | public class MaterialConverter : Editor 10 | { 11 | [MenuItem("Assets/Convert Material(s)")] 12 | static void ConvertMaterials() 13 | { 14 | var shaders = Selection.objects.Where(o => o as Shader != null).Select(o => o as Shader); 15 | var materials = Selection.objects.Where(o => o as Material != null).Select(o => o as Material); 16 | foreach (var m in materials) 17 | { 18 | if (!shaders.Contains(m.shader)) 19 | { 20 | shaders = shaders.Append(m.shader); 21 | } 22 | } 23 | MaterialConverterWindow.Open(shaders); 24 | } 25 | 26 | [MenuItem("Assets/Convert Material(s)", true)] 27 | static bool ValidateConvertMaterials() 28 | { 29 | return Selection.objects.Where(o => o as Shader != null || o as Material != null).Count() > 0; 30 | } 31 | } 32 | 33 | public class MaterialConverterWindow : EditorWindow 34 | { 35 | private List shaderMappings; 36 | private Vector2 scrollPos; 37 | 38 | public class ShaderProp 39 | { 40 | public string name; 41 | public ShaderUtil.ShaderPropertyType type; 42 | public object value; 43 | 44 | public ShaderProp(string _name, ShaderUtil.ShaderPropertyType _type) 45 | { 46 | name = _name; 47 | type = _type; 48 | value = null; 49 | } 50 | } 51 | 52 | public class ShaderMapping 53 | { 54 | public AnimBool show; 55 | public Shader sourceShader; 56 | public Shader targetShader; 57 | public ShaderProp[] sourceShaderProps; 58 | public Dictionary> targetShaderProps; 59 | public Dictionary propertyMapping; 60 | 61 | public ShaderMapping(Shader _shader, EditorWindow window) 62 | { 63 | sourceShader = _shader; 64 | int propCount = ShaderUtil.GetPropertyCount(_shader); 65 | sourceShaderProps = new ShaderProp[propCount]; 66 | for (int i = 0; i < propCount; i++) 67 | { 68 | string propName = ShaderUtil.GetPropertyName(_shader, i); 69 | ShaderUtil.ShaderPropertyType propType = ShaderUtil.GetPropertyType(_shader, i); 70 | sourceShaderProps[i] = new ShaderProp(ShaderUtil.GetPropertyName(_shader, i), ShaderUtil.GetPropertyType(_shader, i)); 71 | } 72 | targetShader = null; 73 | targetShaderProps = new Dictionary>(); 74 | propertyMapping = new Dictionary(); 75 | show = new AnimBool(true); 76 | show.valueChanged.AddListener(window.Repaint); 77 | } 78 | 79 | public void AssignTarget(Shader _shader) 80 | { 81 | targetShader = _shader; 82 | int propCount = ShaderUtil.GetPropertyCount(_shader); 83 | targetShaderProps.Clear(); 84 | propertyMapping.Clear(); 85 | for (int i = 0; i < propCount; i++) 86 | { 87 | string propName = ShaderUtil.GetPropertyName(_shader, i); 88 | var propType = ShaderUtil.GetPropertyType(_shader, i); 89 | var shaderProp = new ShaderProp(ShaderUtil.GetPropertyName(_shader, i), ShaderUtil.GetPropertyType(_shader, i)); 90 | if (!targetShaderProps.ContainsKey(shaderProp.type)) 91 | { 92 | targetShaderProps.Add(shaderProp.type, new List()); 93 | } 94 | targetShaderProps[shaderProp.type].Add(shaderProp); 95 | } 96 | foreach (var prop in sourceShaderProps) 97 | { 98 | if (!targetShaderProps.ContainsKey(prop.type)) 99 | { 100 | continue; 101 | } 102 | int index = targetShaderProps[prop.type].FindIndex(o => o.name == prop.name && o.type == prop.type); 103 | if (index >= 0) 104 | { 105 | propertyMapping[prop] = targetShaderProps[prop.type][index]; 106 | } 107 | } 108 | } 109 | } 110 | 111 | public static void Open(IEnumerable shaders) 112 | { 113 | var window = GetWindow(); 114 | window.shaderMappings = new List(); 115 | foreach (var shader in shaders) 116 | { 117 | var shaderProps = new ShaderMapping(shader, window); 118 | window.shaderMappings.Add(shaderProps); 119 | } 120 | window.Show(); 121 | } 122 | 123 | void OnGUI() 124 | { 125 | Event e = Event.current; 126 | using (var scrollScope = new EditorGUILayout.ScrollViewScope(scrollPos)) 127 | { 128 | scrollPos = scrollScope.scrollPosition; 129 | for (int i = 0; i < shaderMappings.Count; i++) 130 | { 131 | using (var vScope = new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) 132 | { 133 | using (var hScope = new EditorGUILayout.HorizontalScope()) 134 | { 135 | string prefix = shaderMappings[i].show.target ? "-" : "+"; 136 | EditorGUILayout.LabelField($"{prefix} {shaderMappings[i].sourceShader.name}", EditorStyles.boldLabel); 137 | if (EditorGUILayout.DropdownButton(new GUIContent(shaderMappings[i].targetShader ? shaderMappings[i].targetShader.name : "Target Shader"), FocusType.Passive)) 138 | { 139 | var menu = new GenericMenu(); 140 | var infos = ShaderUtil.GetAllShaderInfo(); 141 | foreach (var info in infos) 142 | { 143 | var j = i; 144 | menu.AddItem(new GUIContent(info.name), shaderMappings[i].targetShader && shaderMappings[i].targetShader.name.Equals(info.name), () => 145 | { 146 | shaderMappings[j].AssignTarget(Shader.Find(info.name)); 147 | }); 148 | } 149 | menu.ShowAsContext(); 150 | } 151 | } 152 | Rect r = GUILayoutUtility.GetLastRect(); 153 | if (e.type == EventType.MouseDown && e.button == 0 && r.Contains(e.mousePosition)) 154 | { 155 | shaderMappings[i].show.target = !shaderMappings[i].show.target; 156 | } 157 | using (var fadeScope = new EditorGUILayout.FadeGroupScope(shaderMappings[i].show.faded)) 158 | { 159 | if (fadeScope.visible) 160 | { 161 | if (shaderMappings[i].targetShader == null) 162 | { 163 | continue; 164 | } 165 | if (shaderMappings[i].targetShader == shaderMappings[i].sourceShader) 166 | { 167 | continue; 168 | } 169 | EditorGUI.indentLevel++; 170 | using (var vScope2 = new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) 171 | { 172 | foreach (var prop in shaderMappings[i].sourceShaderProps) 173 | { 174 | var j = i; 175 | using (var hScope = new EditorGUILayout.HorizontalScope()) 176 | { 177 | EditorGUILayout.LabelField(prop.name); 178 | if (shaderMappings[i].targetShaderProps.ContainsKey(prop.type)) 179 | { 180 | if (EditorGUILayout.DropdownButton(new GUIContent(shaderMappings[i].propertyMapping.ContainsKey(prop) ? shaderMappings[i].propertyMapping[prop].name : "None"), FocusType.Passive)) 181 | { 182 | var menu = new GenericMenu(); 183 | menu.AddItem(new GUIContent("None"), false, () => 184 | { 185 | shaderMappings[j].propertyMapping.Remove(prop); 186 | }); 187 | foreach (var p in shaderMappings[i].targetShaderProps[prop.type]) 188 | { 189 | menu.AddItem(new GUIContent(p.name), false, () => 190 | { 191 | shaderMappings[j].propertyMapping[prop] = p; 192 | }); 193 | } 194 | menu.ShowAsContext(); 195 | } 196 | } 197 | else 198 | { 199 | EditorGUILayout.LabelField($"No {prop.type} properties"); 200 | } 201 | } 202 | } 203 | } 204 | EditorGUI.indentLevel--; 205 | using (var hScope = new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) 206 | { 207 | GUILayout.FlexibleSpace(); 208 | if (GUILayout.Button(new GUIContent("Material List", "List of materials that will be converted"))) 209 | { 210 | var materials = AssetDatabase.FindAssets("t:Material").Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))).Where(m => m.shader == shaderMappings[i].sourceShader); 211 | MaterialConverterListWindow.Open(materials.ToArray()); 212 | } 213 | if (GUILayout.Button("Convert Materials")) 214 | { 215 | var targetShader = shaderMappings[i].targetShader; 216 | var materials = AssetDatabase.FindAssets("t:Material").Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))).Where(m => m.shader == shaderMappings[i].sourceShader); 217 | foreach (var mat in materials) 218 | { 219 | foreach (var key in shaderMappings[i].propertyMapping.Keys) 220 | { 221 | switch (key.type) 222 | { 223 | case ShaderUtil.ShaderPropertyType.Color: 224 | shaderMappings[i].propertyMapping[key].value = mat.GetColor(key.name); 225 | break; 226 | case ShaderUtil.ShaderPropertyType.Float: 227 | case ShaderUtil.ShaderPropertyType.Range: 228 | shaderMappings[i].propertyMapping[key].value = mat.GetFloat(key.name); 229 | break; 230 | case ShaderUtil.ShaderPropertyType.TexEnv: 231 | shaderMappings[i].propertyMapping[key].value = mat.GetTexture(key.name); 232 | break; 233 | case ShaderUtil.ShaderPropertyType.Vector: 234 | shaderMappings[i].propertyMapping[key].value = mat.GetVector(key.name); 235 | break; 236 | } 237 | } 238 | mat.shader = shaderMappings[i].targetShader; 239 | foreach (var key in shaderMappings[i].propertyMapping.Keys) 240 | { 241 | switch (key.type) 242 | { 243 | case ShaderUtil.ShaderPropertyType.Color: 244 | // Debug.Log($"SetColor: {(Color)shaderMappings[i].propertyMapping[key].value}"); 245 | mat.SetColor(shaderMappings[i].propertyMapping[key].name, (Color)shaderMappings[i].propertyMapping[key].value); 246 | break; 247 | case ShaderUtil.ShaderPropertyType.Float: 248 | case ShaderUtil.ShaderPropertyType.Range: 249 | // Debug.Log($"SetFloat: {(float)shaderMappings[i].propertyMapping[key].value}"); 250 | mat.SetFloat(shaderMappings[i].propertyMapping[key].name, (float)shaderMappings[i].propertyMapping[key].value); 251 | break; 252 | case ShaderUtil.ShaderPropertyType.TexEnv: 253 | // Debug.Log($"SetTexture: {(Texture)shaderMappings[i].propertyMapping[key].value}"); 254 | mat.SetTexture(shaderMappings[i].propertyMapping[key].name, (Texture)shaderMappings[i].propertyMapping[key].value); 255 | break; 256 | case ShaderUtil.ShaderPropertyType.Vector: 257 | // Debug.Log($"SetVector: {(Vector4)shaderMappings[i].propertyMapping[key].value}"); 258 | mat.SetVector(shaderMappings[i].propertyMapping[key].name, (Vector4)shaderMappings[i].propertyMapping[key].value); 259 | break; 260 | } 261 | } 262 | } 263 | shaderMappings[i] = new ShaderMapping(targetShader, this); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | } 270 | } 271 | } 272 | } 273 | 274 | public class MaterialConverterListWindow : EditorWindow 275 | { 276 | Material[] materials; 277 | Vector2 scrollPos; 278 | 279 | public static void Open(Material[] _materials) 280 | { 281 | var window = EditorWindow.GetWindow(); 282 | window.materials = _materials; 283 | window.Show(); 284 | } 285 | 286 | void OnGUI() 287 | { 288 | EditorGUILayout.LabelField($"{materials.Length} Materials", EditorStyles.boldLabel); 289 | using (var scrollScope = new EditorGUILayout.ScrollViewScope(scrollPos)) 290 | { 291 | scrollPos = scrollScope.scrollPosition; 292 | foreach (var mat in materials) 293 | { 294 | EditorGUILayout.ObjectField(mat, typeof(Material), false); 295 | } 296 | } 297 | } 298 | } --------------------------------------------------------------------------------