Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

DrawMeshInstanced option to provide per instance texture via material property block?

Discussion in 'Scripting' started by NathanJSmith, Sep 18, 2018.

  1. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    I need to draw a large group of people with different texture for each person using funtion DrawMeshInstanced().
    I see that DrawMeshInstanced supports MaterialPropertyBlock, and there is method MaterialPropertyBlock.SetTexture() to set texture property for material.
    According to this post:
    zeroyao: "Using arrays in the single MaterialPropertyBlock is the preferred way to pass per-instance data to DrawMeshInstanced. In places where each renderer has a MaterialPropertyBlock (DrawMesh, or just regular MeshRenderers being batched) the internal instancing code goes through a loop to collect all data into one single MaterialPropertyBlock, and that is slower than doing that explicitly in scripting."
    But I can't find any method to set texture array (but there is method to SetFloatArray, SetMatrixArray,...)
    So Is there DrawMeshInstanced option to provide per instance texture via material property block?
     
  2. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    Check this: GPU Instancing + Texture2DArray.

    The demo in link above is pretty outdated in Unity 2018. I rewrite a little to make it work.
    MaterialTextureChanger.shader
    Code (CSharp):
    1.  
    2. Shader "Custom/MaterialTextureChanger" {
    3.     Properties
    4.     {
    5.        _TextureIdx("Texture Idx", float) = 0.0
    6.         _Textures("Textures", 2DArray) = "" {}
    7.     }
    8.  
    9.     SubShader
    10.     {
    11.         Tags { "RenderType"="Opaque" }
    12.  
    13.         CGPROGRAM
    14.  
    15.         #pragma surface surf Standard fullforwardshadows
    16.         #pragma target 3.5
    17.         #include "UnityCG.cginc"
    18.  
    19.         UNITY_DECLARE_TEX2DARRAY(_Textures);
    20.  
    21.         struct Input
    22.         {
    23.             fixed2 uv_Textures;
    24.         };
    25.  
    26.        UNITY_INSTANCING_BUFFER_START(Props)
    27.        UNITY_DEFINE_INSTANCED_PROP(float, _TextureIdx)
    28.        UNITY_INSTANCING_BUFFER_END(Props)
    29.  
    30.         void surf (Input IN, inout SurfaceOutputStandard o)
    31.         {
    32.            fixed4 c = UNITY_SAMPLE_TEX2DARRAY(
    33.                _Textures,
    34.                float3(IN.uv_Textures, UNITY_ACCESS_INSTANCED_PROP(Props, _TextureIdx))
    35.            );
    36.             o.Albedo = c.rgb;
    37.             o.Alpha = c.a;
    38.         }
    39.  
    40.         ENDCG
    41.     }
    42.     FallBack "Diffuse"
    43. }
    44.  
    45.  
    MaterialTextureChanger.cs: Note: Assign 2 textures for the array: textures
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MaterialTextureChanger : MonoBehaviour
    6. {
    7.     public Texture2D[] textures;
    8.  
    9.     private Renderer m_renderer;
    10.     private MaterialPropertyBlock m_mpb;
    11.     float[] m_arrTextureArr;
    12.  
    13.     float m_deltaCount = 0.0f;
    14.  
    15.     void Start()
    16.     {
    17.         m_renderer = GetComponentInChildren<Renderer>();
    18.         Material material = m_renderer.sharedMaterial;
    19.  
    20.         int textureWidth = textures[0].width;
    21.         int textureHeight = textures[0].height;
    22.         TextureFormat textFormat = textures[0].format;
    23.  
    24.         Texture2DArray textureArray = new Texture2DArray(textureWidth, textureHeight, textures.Length, textFormat, false);
    25.  
    26.         for (int i = 0; i < textures.Length; i++)
    27.         {
    28.             Graphics.CopyTexture(textures[i], 0, 0, textureArray, i, 0); // i is the index of the texture
    29.         }
    30.  
    31.         material.SetTexture("_Textures", textureArray);
    32.         m_arrTextureArr = new float[1];
    33.  
    34.         m_mpb = new MaterialPropertyBlock();
    35.         // Get the current value of the material properties in the renderer.
    36.         m_renderer.GetPropertyBlock(m_mpb, 0);
    37.         // Assign our new value.
    38.         m_arrTextureArr[0] = 1;
    39.         m_mpb.SetFloatArray("_TextureIdx", m_arrTextureArr);
    40.         // Apply the edited values to the renderer.
    41.         m_renderer.SetPropertyBlock(m_mpb, 0);
    42.     }
    43.  
    44.     private void Update()
    45.     {
    46.         m_deltaCount += Time.deltaTime;
    47.         if(m_deltaCount > 3.0f)
    48.         {
    49.             m_deltaCount = 0.0f;
    50.  
    51.             // Get the current value of the material properties in the renderer.
    52.             m_renderer.GetPropertyBlock(m_mpb, 0);
    53.             // Assign our new value.
    54.             m_arrTextureArr[0] = m_arrTextureArr[0] == 1 ? 0 : 1;
    55.             m_mpb.SetFloatArray("_TextureIdx", m_arrTextureArr);
    56.             // Apply the edited values to the renderer.
    57.             m_renderer.SetPropertyBlock(m_mpb, 0);
    58.         }
    59.     }
    60. }
    61.  
    Here is the manual for TextureArray in shader file.
     
    Last edited: Sep 18, 2018
  3. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    Here is the demo for DrawMeshInstanced option to provide per instance texture via material property block:

    MaterialTextureChanger.shader
    Code (CSharp):
    1. Shader "Custom/MaterialTextureChanger" {
    2.     Properties
    3.     {
    4.         _TextureIdx("Texture Idx", float) = 0.0
    5.         _Textures("Textures", 2DArray) = "" {}
    6.     }
    7.  
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.  
    12.         CGPROGRAM
    13.  
    14.         #pragma surface surf Standard fullforwardshadows
    15.         #pragma target 3.5
    16.         #include "UnityCG.cginc"
    17.  
    18.         UNITY_DECLARE_TEX2DARRAY(_Textures);
    19.  
    20.         struct Input
    21.         {
    22.             fixed2 uv_Textures;
    23.         };
    24.  
    25.         UNITY_INSTANCING_BUFFER_START(Props)
    26.         UNITY_DEFINE_INSTANCED_PROP(float, _TextureIdx)
    27.         UNITY_INSTANCING_BUFFER_END(Props)
    28.  
    29.         void surf (Input IN, inout SurfaceOutputStandard o)
    30.         {
    31.             fixed4 c = UNITY_SAMPLE_TEX2DARRAY(
    32.                 _Textures,
    33.                 float3(IN.uv_Textures, UNITY_ACCESS_INSTANCED_PROP(Props, _TextureIdx))
    34.             );
    35.             o.Albedo = c.rgb;
    36.             o.Alpha = c.a;
    37.         }
    38.  
    39.         ENDCG
    40.     }
    41.     FallBack "Diffuse"
    42. }
    43.  
    MaterialTextureChanger.cs: Note: Assign 2 textures for the array: m_textures

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MaterialTextureChanger : MonoBehaviour
    6. {
    7.     public Texture2D[] m_textures;
    8.     public Mesh m_instanceMesh;
    9.     public Material m_instanceMaterial;
    10.  
    11.     private Matrix4x4[] m_transformList;
    12.     private Renderer m_renderer;
    13.     private MaterialPropertyBlock m_mpb;
    14.     private float[] m_arrTextureArr;
    15.     private float m_deltaCount = 0.0f;
    16.  
    17.     const int INSTANCE_COUNT = 50;
    18.  
    19.     void Start()
    20.     {
    21.         int textureWidth = m_textures[0].width;
    22.         int textureHeight = m_textures[0].height;
    23.         TextureFormat textFormat = m_textures[0].format;
    24.  
    25.         Texture2DArray textureArray = new Texture2DArray(textureWidth, textureHeight, m_textures.Length, textFormat, false);
    26.  
    27.         for (int i = 0; i < m_textures.Length; i++)
    28.         {
    29.             Graphics.CopyTexture(m_textures[i], 0, 0, textureArray, i, 0); // i is the index of the texture
    30.         }
    31.  
    32.         m_instanceMaterial.SetTexture("_Textures", textureArray);
    33.  
    34.        
    35.         m_transformList = new Matrix4x4[INSTANCE_COUNT];
    36.         Vector3 pos = Vector3.zero;
    37.  
    38.         m_arrTextureArr = new float[INSTANCE_COUNT];
    39.  
    40.         for (int instance = 0; instance < INSTANCE_COUNT; ++instance)
    41.         {
    42.             pos.Set(
    43.                     Random.Range(-5.0f, 5.0f),
    44.                     1.0f,
    45.                     Random.Range(7.0f, 15.0f)
    46.                 );
    47.             m_transformList[instance].SetTRS(
    48.                 pos,
    49.                 Quaternion.Euler(0.0f, Random.Range(0.0f, 360.0f), 0.0f),
    50.                 Vector3.one*100
    51.                 );
    52.  
    53.             m_arrTextureArr[instance] = instance % 2 == 0 ? 1.0f : 0.0f;
    54.         }
    55.  
    56.        
    57.         m_mpb = new MaterialPropertyBlock();
    58.  
    59.         ChangeTexture();
    60.     }
    61.  
    62.     void ChangeTexture()
    63.     {
    64.         for (int instance = 0; instance < INSTANCE_COUNT; ++instance)
    65.             m_arrTextureArr[instance] = m_arrTextureArr[instance] == 1.0f ? 0.0f : 1.0f;
    66.  
    67.         m_mpb.SetFloatArray("_TextureIdx", m_arrTextureArr);
    68.     }
    69.  
    70.     private void LateUpdate()
    71.     {
    72.         m_deltaCount += Time.deltaTime;
    73.         if(m_deltaCount > 3.0f)
    74.         {
    75.             m_deltaCount = 0.0f;
    76.  
    77.             ChangeTexture();
    78.         }
    79.  
    80.         Graphics.DrawMeshInstanced(m_instanceMesh, 0, m_instanceMaterial, m_transformList, m_transformList.Length, m_mpb, UnityEngine.Rendering.ShadowCastingMode.Off, false);
    81.     }
    82. }
    83.  
     
    DevilZ1976 likes this.
  4. NathanJSmith

    NathanJSmith

    Joined:
    May 11, 2018
    Posts:
    57
    Also, while working with instancing to vertex and fragment Shaders (the shader file has `v2f vert (float4 vertex : POSITION)`, `half4 frag (v2f i) : SV_Target`,...), you may meet the problem that the MaterialPropertyBlock doesn't work properly. If you meet that problem, check `UNITY_VERTEX_INPUT_INSTANCE_ID` & `UNITY_SETUP_INSTANCE_ID` & `UNITY_TRANSFER_INSTANCE_ID` at Adding instancing to vertex and fragment Shaders.