Unity-Technologies / Animation-Instancing

This technique is designed to instance Characters(SkinnedMeshRender).
Other
1.63k stars 296 forks source link

Can I set different color for materials? #87

Open sharyu opened 4 years ago

sharyu commented 4 years ago

Hi, I want to use same model and animation, but different colors. Is it possible?

ashwin911 commented 3 years ago

Yes. You need to use a texture array and even if you just want to use pure colors I still recommend using different textures as color palettes and then you sample the texture in the shader to get the different colors. It's much more performant than sending a whole bunch of colors to the meshes.

first create a script which will create and apply the texture array to the materials. The different textures need to have the same dimensions, for example a 8x8 color palette. And the textures need to be in the same format, for example RGB 24bit .

public class TextureArray : MonoBehaviour
{
        public Texture2D[] textures;
        public Material[] materials;
        public bool applyOnStart = true;
        private string TexArrayStringName = "_TextureArray";
        private int texArrayHashId;
        private Texture2DArray textureArray;
        public void Start()
        {
            texArrayHashId = Shader.PropertyToID(TexArrayStringName);

            if (applyOnStart)
            {
                SetTextures();
            }
        }

        public void SetTextures()
        {
            int textureWidth = textures[0].width;
            int textureHeight = textures[0].height;
            var format = textures[0].format;
            textureArray = 
                new Texture2DArray(textureWidth, textureHeight, textures.Length, format, false);

            for (int i = 0; i < textures.Length; i++)
            {
                Graphics.CopyTexture(textures[i], 0, 0, textureArray, i, 0); // i is the index of the texture
            }

            for (int i = 0; i < materials.Length; i++)
            {
                SetTexArray(materials[i]);

            }

        }

        public void SetTexArray(Material mat)
        {
            mat.SetTexture(texArrayHashId, textureArray);
        }

}

In AnimationInstancing.cs add a new variable. This is where you change the texture index for each character.

public class AnimationInstancing : MonoBehaviour
{
        public int texturArrIndex;
        ...

In AnimationInstancingMgr.cs add these things (every line under "//Add this " comment):

  public class AnimationInstancingMgr : Singleton<AnimationInstancingMgr>
  {
      // array[index base on texture][package index][instance index]
      public struct InstanceData
      {
          ...
          //Add this
          public List<float[]>[] texureArrayIndex;
      }

//Add this
        private static readonly int TexArrIndexHash = Shader.PropertyToID("_TextureIndex");

      ...

      private void Render()
      {
          foreach (var obj in vertexCachePool)
          {
              VertexCache vertexCache = obj.Value;
              foreach (var block in vertexCache.instanceBlockList)
              {
                  List<InstancingPackage>[] packageList =
                      block.Value.packageList;
                  for (int k = 0; k != packageList.Length; ++k)
                  {
                      for (int i = 0; i != packageList[k].Count; ++i)
                      {
                          InstancingPackage package = packageList[k][i];
                          if (package.instancingCount == 0)
                              continue;
                          for (int j = 0; j != package.subMeshCount; ++j)
                          {
                              InstanceData data = block.Value.instanceData;
                              if (useInstancing)
                              {
#if UNITY_EDITOR
                                    PreparePackageMaterial(package, vertexCache, k);
#endif
                                  //Add this
                                  package.propertyBlock.SetFloatArray(TexArrIndexHash , data.texureArrayIndex[k][i]);
                                  ....
                              }
                              else
                              {
                                  ...
                              }
                          }
                          ...
                      }
                      ...
                  }
              }
          }
      }

      void ApplyBoneMatrix()
      {
          ...
          for (int i = 0; i != aniInstancingList.Count; ++i)
          {
               AnimationInstancing instance = aniInstancingList[i];
                  ...

                  for (int j = 0; j != lod.vertexCacheList.Length; ++j)
                 {
                      ...
                     else
                        ++package.instancingCount;
                     {
                         ...
                         if (count >= 0)
                         {
                             ...

                             data.transitionProgress[aniTextureIndex][index][count] = transition;

                             //Add this
                             data.texureArrayIndex[aniTextureIndex][index][count]= instance.texturArrIndex;

                         }
                     }

              }

              ...
          }

          ...
      }
      ...
      public InstancingPackage CreatePackage(InstanceData data, Mesh mesh,
          Material[] originalMaterial, int animationIndex)
      {
          ...
          //Add this
          data.texureArrayIndex[animationIndex].Add(new float[instancingPackageSize]);

          return package;
      }
      ...
      InstanceData CreateInstanceData(int packageCount)
      {
          ...
          //Add this
          data.texureArrayIndex = new List<float[]>[packageCount];

          for (int i = 0; i != packageCount; ++i)
          {
              //Add this
              data.texureArrayIndex[i] = new List<float[]>();
          }

          return data;
      }
  }

in your shader you need to add this

UNITY_DECLARE_TEX2DARRAY(_TextureArray);

UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_DEFINE_INSTANCED_PROP(float, _TextureIndex)    
        #define  _TexureIndex_arr Props  

UNITY_INSTANCING_BUFFER_END(Props)

and to sample the texture in the fragment shader

  int index = UNITY_ACCESS_INSTANCED_PROP(_TexureIndex_arr , _TextureIndex);
  float4 color = UNITY_SAMPLE_TEX2DARRAY(_TextureArray, half3(IN.uv,  index));

if you use the texture as a palette where each pixel is a different color and you want to sample one color you could use a function like this

float4 GetPalletteColor( int x, int y, int texDimension)
{
     int index = UNITY_ACCESS_INSTANCED_PROP(_TexureIndex_arr , _TextureIndex);
     float pixelSize = 1/texDimension;
     float halfPixSize = pixelSize * 0.5;

     return UNITY_SAMPLE_TEX2DARRAY(_TextureArray, half3(halfPixSize + x * pixelSize, halfPixSize + y * pixelSize,  index));
}

this is useful if you use an RGB mask, so if the mask is black you sample pixel (0,0) if it's red pixel(0,1) etc.