- Home /
A way to iterate/enumerate shader properties?
I'm trying to automatically scan materials in my project from an editor script to find usages of a given texture.
I was able to successfully iterate all materials using the assets database. However when it comes to the question of "is this material using this texture" I've come across a problem since the answer to that depends on the shader, and each shader has a different set of string properties.
My question is, is there a way to get all the properties of a given shader so that I can iterate each one and check its value?
Edit: Alternatively, is there a place somewhere where I can find all the properties used by Unity's shaders? or the code of all those shaders so that I can extract a definitive list of all used property names?
Answer by whydoidoit · Jan 28, 2013 at 06:37 PM
EDIT: As of 4.1 ShaderUtils is now a public documented class. You no longer need this code from that version of Unity onwards.
You can get them in an Editor script by using the hidden ShaderUtils class. I've written a wrapper which is a C# extension class for material allowing you to get the properties, their count, type and name. Plus useful stuff like all of the referenced textures.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Collections;
public static class ShaderUtilInterface
{
public static Dictionary<string, MethodInfo> methods = new Dictionary<string, MethodInfo>();
static ShaderUtilInterface()
{
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a=>a.GetTypes().Any(t=>t.Name == "ShaderUtil"));
if(asm != null)
{
var tp = asm.GetTypes().FirstOrDefault(t=>t.Name == "ShaderUtil");
foreach(var method in tp.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static))
{
methods[method.Name] = method;
}
}
}
public static List<Texture> GetTextures(this Material shader)
{
var list = new List<Texture>();
var count = shader.GetPropertyCount();
for(var i = 0; i < count; i++)
{
if(shader.GetPropertyType(i)==4)
{
list.Add((Texture)shader.GetProperty(i));
}
}
return list;
}
public static int GetPropertyCount(this Material shader)
{
return Call<int>("GetPropertyCount", shader.shader);
}
public static int GetPropertyType(this Material shader, int index)
{
return Call<int>("GetPropertyType", shader.shader, index);
}
public static string GetPropertyName(this Material shader, int index)
{
return Call<string>("GetPropertyName", shader.shader, index);
}
public static void SetProperty(this Material material, int index, object value)
{
var name = material.GetPropertyName(index);
var type = material.GetPropertyType(index);
switch(type)
{
case 0:
material.SetColor(name, (Color)value);
break;
case 1:
material.SetVector(name, (Vector4) value);
break;
case 2:
material.SetFloat(name, (float)value);
break;
case 3:
material.SetFloat(name, (float)value);
break;
case 4:
material.SetTexture(name, (Texture) value);
break;
}
}
public static object GetProperty(this Material material, int index)
{
var name = material.GetPropertyName(index);
var type = material.GetPropertyType(index);
switch(type)
{
case 0:
return material.GetColor(name);
case 1:
return material.GetVector(name);
case 2:
case 3:
return material.GetFloat(name);
case 4:
return material.GetTexture(name);
}
return null;
}
public static T Call<T>(string name, params object[] parameters)
{
return (T)methods[name].Invoke(null, parameters);
}
}
Here's an example of using it:
var materialTextures = new Dictionary< Texture, bool>();
foreach (var m in all$$anonymous$$aterials)
{
foreach(var t in m.GetTextures())
{
materialTextures[t] = false;
}
}
var textureLookup = materialTextures.$$anonymous$$eys.Where(c=>!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(c))).GroupBy(c=>c.name).ToDictionary(c=>c.$$anonymous$$ey, c=>c.ToLookup(f=>new FileInfo(AssetDatabase.GetAssetPath(f)).Length));
var usedTextures = new Dictionary();
foreach(var m in all$$anonymous$$aterials)
{
for(var i = 0; i < m.GetPropertyCount(); i++)
{
if(m.GetPropertyType(i)==4)
{
var currentTexture = m.GetProperty(i) as Texture;
if(string.IsNullOrEmpty(AssetDatabase.GetAssetPath(currentTexture)))
continue;
var newTexture = textureLookup[currentTexture.name][new FileInfo(AssetDatabase.GetAssetPath(currentTexture)).Length].First();
m.SetProperty(i,newTexture);
materialTextures.Remove(newTexture);
usedTextures[newTexture] = true;
}
}
EditorUtility.SetDirty(m);
}
Nice ;) just what i need. Well designed (except the magic "type-numbers" :D)
+1
ps: what's the difference between type 2 and 3? I mean inside the shader...
edit
Never$$anonymous$$d, got it ;)
internal enum ShaderPropertyType
{
Color,
Vector,
Float,
Range,
TexEnv
}
Hey, whydoidoit thanks for you example, I found that shaderUtil class right now is open and api available here http://docs.unity3d.com/Documentation/ScriptReference/ShaderUtil.html just didn't find ShaderPropertyType, so thanks to Bunny83
and here is my implementation of the listTexturesBy$$anonymous$$aterial method
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
public class UFT$$anonymous$$aterialUtil {
public static List<Texture> getTextures($$anonymous$$aterial material){
string assetPath=AssetDatabase.GetAssetPath(material);
if (String.IsNullOrEmpty(assetPath))
return null;
Shader shader=material.shader;
List<Texture> materials=new List<Texture>();
for (int i = 0; i < ShaderUtil.GetPropertyCount(shader) ; i++) {
if (ShaderUtil.GetPropertyType(shader,i) == ShaderUtil.ShaderPropertyType.TexEnv){
materials.Add(material.GetTexture(ShaderUtil.GetPropertyName(shader,i)));
}
}
return materials;
}
}
Answer by amirabiri · Oct 23, 2011 at 02:11 PM
OK, I should have thought of the 2nd option myself first:
http://www.unity3d.com/support/resources/assets/built-in-shaders
Edit: For anyone reading this who is looking for the "definitive" list of the texture shader property of Unity's built-in properties:
{ "_BackTex", "_BumpMap", "_BumpSpecMap", "_Control", "_DecalTex",
"_Detail", "_DownTex", "_FrontTex", "_GlossMap", "_Illum", "_LeftTex",
"_LightMap", "_LightTextureB0", "_MainTex", "_ParallaxMap", "_RightTex",
"_ShadowOffset", "_Splat0", "_Splat1", "_Splat2", "_Splat3",
"_TranslucencyMap", "_UpTex", "_Tex", "_Cube" };
Note that this true as of right now, and could change if Unity's shaders change in the future.
This is the shell command I used to extract the property names:
pcregrep -r -M 'Properties\s*{.*(\n.+)*\n}' -N anycrlf * | grep 2D | awk '{print $1}' | sort | uniq
Your answer
Follow this Question
Related Questions
The name 'Joystick' does not denote a valid type ('not found') 2 Answers
How can I change default shader for new/imported objects? 1 Answer
Material doesn't have a color property '_Color' 4 Answers
Display material only in editor 2 Answers
Is it possible to change the shader the editor uses? 2 Answers