/**************************************************************************** * * * Copyright (c) 2010, 2010 All Rights Reserved, http://klakos.com * * LICENCE : GPLv2 [http://www.gnu.org/licenses/gpl-2.0.html] * * This source code is intended only as a supplement to Unity * Development Tools and/or on-line documentation. See these other * materials for detailed information regarding Unity code samples. * * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY * KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A * PARTICULAR PURPOSE. * * * * Gauthier BOAGLIO * gauthier __at__ klakos.com * 2011-05-10 * * * Basic tool (Unity [Copyright (c)] 3.3.0f3 addon) for easy setup of * Double sided / Dual Materials Shading Technics - MORE INFORMATIONS * HERE : http://klakos.com/?p=2020, [ARTICLE : Advanced Waving Flag * Shader for Unity (Double sided, Alpha shadow support)] * * * *************************************************************************/ /**************************************************************************** * * * Note : Regular => Backward only... * ---- * * Install : * ------- * Just drop the “BackwardMaterialBuilder.cs” (ScriptableWizard) script into * the “Assets/Editor” directory of the Unity project. * * Prerequisites : * ------------- * * 1- Naming conventions for shaders : * - Front/Forward shaders Name has to end with “Regular” suffix * - Back/Backward shaders Name has to end with “Backward” suffix * * Those conventions deal with the Shaders name (not the Materials name) * * [CODE] * * Shader "Selfmade/for-2sided/FlagWave Advanced Regular" * { * * Properties * { * // Usual stuffs * _Color ("Main Color", Color) = (1,1,1,1) * _SpecColor ("Specular Color", Color) = (0.5, 0.5, 0.5, 0) * . * . * . * [/CODE] * * 2- Material assets need to be made by hand and this tool will only * help to set them up correctly. * * 3- The different shaders may have been previously created. * * Usage : * ----- * To access the script, select an object (in the Hierarchy or Project pane). * Then right-click on the context menu icon of the * Renderer (either Mesh or SkinnedMesh Renderer), and choose “Set 2-sided Materials” : * The related window displays a selectable list of all the “Regular” shaded * materials from the Renderer materials list : * Then select one of those Materials and press “Proceed”. * * If the corresponding “Backward” Shader exists in you Project, 3 different * things may happen : * * - Synchronize props form “Regular” to “Backward”, if Mats are existing * - Create the “Backward” one if not existing (not setting the Shader) * - Erase or not the actual Shader of the current “Target Backup Material” * * Then drop a hand made target Material of your choice from your “Assets” folder * into the “Target Backup Material” field. * * Option “Force Shader”, will set the right existing “Backward” Shader to the * targeted Material. * If unchecked, the Shader of the original dropped Material will remain the same. * In many cases, this option has to be checked (but you'll lose all the current * settings of the target Material). * * * * *************************************************************************/ using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; using System.IO; public class BackwardMaterialBuilder : ScriptableWizard { # region Main Window Handler private static BackwardMaterialBuilder m_Win; # endregion # region Operating Stuffs public static bool doCreateBWMaterial = false; public static Renderer targetRenderer; private Material regularMaterial; private Material backwardMaterial; List materials = new List(); static string FRONT_FACE_SUFFIX = "Regular"; static string BACK_FACE_SUFFIX = "Backward"; // Target material private Material matToSaveTo; private bool showMaterialCreatorWindow = false; private bool forceShader = false; # endregion # region GUI stuffs Rect windowRect = new Rect(100, 100, 500, 200); Vector2 scrollPos = Vector2.zero; int intSelectedMat = 0; Dictionary matsMap = new Dictionary(); List matNames = new List(); # endregion # region Context Menu Settings // Context menu: [MenuItem("CONTEXT/MeshRenderer/Set 2-sided Materials")] static void UpdateBackwardMaterialForMeshRenderer(MenuCommand cmd) { RunWizard(cmd); } // Validator: [MenuItem("CONTEXT/MeshRenderer/Set 2-sided Materials", true)] static bool ValidateBackwardMaterialForMeshRendererContext(MenuCommand cmd) { return cmd.context is MeshRenderer; } // Context menu: [MenuItem("CONTEXT/SkinnedMeshRenderer/Set 2-sided Materials")] static void UpdateBackwardMaterialForSkinnedMeshRenderer(MenuCommand cmd) { RunWizard(cmd); } // Validator: [MenuItem("CONTEXT/SkinnedMeshRenderer/Set 2-sided Materials", true)] static bool ValidateBackwardMaterialForSkinnedMeshRendererContext(MenuCommand cmd) { return cmd.context is SkinnedMeshRenderer; } static void RunWizard(MenuCommand cmd) { BackwardMaterialBuilder.doCreateBWMaterial = (BackwardMaterialBuilder.targetRenderer == null); BackwardMaterialBuilder.targetRenderer = (Renderer)cmd.context; m_Win = (BackwardMaterialBuilder)ScriptableWizard.DisplayWizard("Create & Update Backwards Materials", typeof(BackwardMaterialBuilder)); } # endregion # region Let's Go void OnGUI() { if (m_Win != null) { //GUILayout.BeginScrollView(scrollPos); // Listing "Regular" (Front faces dedicated) Materials // Cleanup List to_remove = new List(); foreach (KeyValuePair kv in matsMap) { if (!MaterialAlreadyExists(kv.Value)) to_remove.Add(kv.Key); } foreach (string mat_name in to_remove) { matsMap.Remove(mat_name); matNames.Remove(mat_name); } // Rebuild foreach (Material mat in targetRenderer.sharedMaterials) { if (!(matsMap.ContainsKey(mat.shader.name)) && IsRegularMaterial(mat)) { matsMap.Add(mat.shader.name, mat); matNames.Add(mat.shader.name); } } // Display if (matNames.Count > 0) intSelectedMat = GUILayout.SelectionGrid(intSelectedMat, matNames.ToArray(), 1); else { GUILayout.Label("Nothing to operate on ! \nThe Renderer of the object \"" + targetRenderer.gameObject.name + "\" must contain at least one Material with a \"Regular\" shader"); if (GUILayout.Button("Abort")) { m_Win.Close(); } return; } GUILayout.Space(50); regularMaterial = matsMap[matNames[intSelectedMat]]; if (GUILayout.Button("Proceed")) { Proceed(); } else if (showMaterialCreatorWindow && m_Win != null) { BeginWindows(); windowRect = GUILayout.Window(1, windowRect, DoMaterialCreatorWindow, "Regular => Backward"); EndWindows(); } //GUILayout.EndScrollView(); } } // Processing void Proceed() { // Synchronization for existing "Regular & Backward" Materials // foreach (Material mat1 in targetrenderer.sharedMaterials) Material mat1 = regularMaterial; if (mat1 != null) { if (mat1.shader.name.EndsWith(FRONT_FACE_SUFFIX)) { Debug.Log("Regular Mat found : " + mat1.shader.name); // Try finding the 'Backward' one Material mat22 = null; foreach (Material mat2 in targetRenderer.sharedMaterials) { string bw_shader_name = mat1.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX); Shader bw_shader = Shader.Find(bw_shader_name); if (mat2 != mat1 && mat2.shader.name == bw_shader_name) { Debug.Log("Backward Mat found : " + mat2.shader.name); // Synchronize & Break if (bw_shader != null) { mat2.shader = bw_shader; mat2.CopyPropertiesFromMaterial(mat1); mat22 = mat2; } break; } } // Do synchronization if (mat22 != null) { EditorUtility.DisplayDialog("Info !", "The Regular material [SHADER : \"" + mat1.shader.name + "\"] \n\nAND \n\nthe Backward " + "material [SHADER : \"" + mat22.shader.name + "\"] \nhave been successfuly synchronized ...\n\n" + "Please give a click in the \"Inspector Tab\" to show the changes ;-)", "Ok"); Debug.Log("Synch. done !"); } // Try to setup the "Backward" Material from existing shader else { bool doCreate = EditorUtility.DisplayDialog("Warning !", "The Regular material \"" + mat1.shader.name + "\" was found," + "\nbut no related Backward one.\n" + "Try to create the Backward material ?", "Ok", "Cancel"); if (doCreate) { // Try finding the right shader string shader_to_find = mat1.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX); Shader shader1 = Shader.Find(shader_to_find); // Shader not found if (shader1 == null) { bool read = EditorUtility.DisplayDialog("Warning !", "The Backward shader \"" + shader_to_find + "\" could not be found !\n" + "RTFM : To create a \"Backward\" shader from any other regular one, just add a \"CULL Front\" " + "instruction at the beginning of your original shader.\n" + "Then, invert the back faces normals by adding a \"-\"n in the normals unpacking instruction. Ex : " + "\"o.Normal = -UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));\" ...", "Ok"); if (read) m_Win.Close(); } // Shader found : continue with Material setup else { regularMaterial = mat1; Debug.Log("Creating material : \"" + shader1.name + "\" ..."); showMaterialCreatorWindow = true; } } } } else { bool try_again = EditorUtility.DisplayDialog("Warning !", "The material named \"" + mat1.shader.name + "\" should be a regular one ...", "Try Again", "Cancel"); if (!try_again) m_Win.Close(); } } else { EditorUtility.DisplayDialog("Warning !", "The material named \"" + mat1.shader.name + "\" should be != null ...", "Ok"); } } // SubWindow : Material creation from existing "Backward" shader public void DoMaterialCreatorWindow(int id) { matToSaveTo = (Material)EditorGUILayout.ObjectField("Target Backup Material : ", matToSaveTo, typeof(Material)); forceShader = GUILayout.Toggle(forceShader, "Force Shader"); if (GUILayout.Button("Proceed")) { if (matToSaveTo != null) { bool setMatOk; if (forceShader) setMatOk = SetMaterialProperties(regularMaterial, true); else setMatOk = SetMaterialProperties(regularMaterial, false); if (!setMatOk) { EditorUtility.DisplayDialog("Error !", "Copy failed because of uncompatible shaders. \nPlease retry with \"Force Shader\" checked ...", "Ok"); showMaterialCreatorWindow = false; return; } backwardMaterial = matToSaveTo; // Update Mats list in Renderer foreach (Material mat in targetRenderer.sharedMaterials) { materials.Add(mat); if (mat == regularMaterial && !MaterialAlreadyExists(backwardMaterial)) materials.Add(backwardMaterial); } targetRenderer.sharedMaterials = materials.ToArray(); showMaterialCreatorWindow = false; m_Win.Close(); } else { string err = ""; if (matToSaveTo == null) err = "The selected material is Null !"; else err = "The material \"" + matToSaveTo.shader.name + "\" is Null or not a \"Backward\" one !"; EditorUtility.DisplayDialog("Warning !", err, "Ok"); } } Repaint(); GUI.DragWindow(); } // Try setting Material properties if Source and Target Materials are Copy-Compatible public bool SetMaterialProperties(Material mat, bool forceShader) { if (forceShader) { string sh_name = mat.shader.name.Replace(FRONT_FACE_SUFFIX, BACK_FACE_SUFFIX); Shader sh = Shader.Find(sh_name); if (sh == null) { EditorUtility.DisplayDialog("Warning !", "The shader [" + sh_name + "] couln't be found \nand will be skipped from material copy !", "Ok"); } else matToSaveTo.shader = sh; } if (!IsSameBasedMaterials(mat, matToSaveTo)) return false; matToSaveTo.CopyPropertiesFromMaterial(mat); return true; } // if mat is tagged as a "Regular" one public bool IsRegularMaterial(Material mat) { return (mat.shader.name.EndsWith(FRONT_FACE_SUFFIX)); } // if mat is tagged as a "Backward" one public bool IsBackwardMaterial(Material mat) { return (mat.shader.name.EndsWith(BACK_FACE_SUFFIX)); } // If mat1 and mat2 are design to be of the same type (means : with the same inner properties) public bool IsSameBasedMaterials(Material mat1, Material mat2) { string mat1name = mat1.shader.name; string mat2name = mat2.shader.name; return (mat1name.Substring(0, mat1name.LastIndexOf(' ')) == mat2name.Substring(0, mat2name.LastIndexOf(' '))); } // If mat is already present in the Renderer materials list public bool MaterialAlreadyExists(Material mat) { bool found = false; foreach (Material m in targetRenderer.sharedMaterials) { if (m == mat) { found = true; break; } } return found; } # endregion }