Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Instancing questions

Discussion in 'General Graphics' started by tawdry, May 3, 2018.

  1. tawdry

    tawdry

    Joined:
    Sep 3, 2014
    Posts:
    1,356
    Hi
    Very confused about instancing trying to make a lot of grass for a game thought id try this approach but ...
    First the meshes only exist for 1 frame? so they have to be constantly called in update is this right or is there some other command than the one im using in the code below that has the mesh continue to exist ?This code slows my fps down to 5 without me looking at the generated meshes.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class drawm : MonoBehaviour {
    5.     public Mesh mesh;
    6.     public Material material;
    7.     int a=0;
    8.  
    9.     public void Update() {
    10.         for (int a = 0; a < 100000; a++) {
    11.             int b = a;
    12.             int c=a;
    13.             transform.position = new Vector3 (b, 0, c);
    14.             Graphics.DrawMesh (mesh, transform.position, Quaternion.identity, material, 0);
    15.         }
    16.          
    17.     }
    18.  
    19.  
    20. }
    I looked on github and found this one project that looks promising it is able to run at 60 fps with 100k meshes and 1 million is need to slow it down to the abovementioned 5 fps.But not too clued in on coding so could someone tell me what about the code helps it do that and can that code be integrated into my above sample?
    Couple issues with this code below.
    First it only works with unity meshes if i try put in any other mesh its invisible.
    Second it only works with its shader if i put in any other shader it doesn't work.
    Third the shader isn't a cutout is it possible to convert it to a cutout shade and will still work with the code?
    And lastly and this totally confuses me the instanceCount amount it used to start with was 100k i tried setting the instancecount to 1000 at initiation but that didn't work so i put the line in the Start which does restrict it but wjhat i don't see is where is instructs the instancecount to be 100k between the initiation line and the start one?
    Code (CSharp):
    1. // https://docs.unity3d.com/560/Documentation/ScriptReference/Graphics.DrawMeshInstancedIndirect.html
    2.  
    3. using UnityEngine;
    4. public class InstancedIndirectExample : MonoBehaviour
    5. {
    6.     public int instanceCount = 1000;
    7.     public Mesh instanceMesh;
    8.     public Material instanceMaterial;
    9.     private int cachedInstanceCount = -1;
    10.     private ComputeBuffer positionBuffer;
    11.     private ComputeBuffer argsBuffer;
    12.     private ComputeBuffer colorBuffer;
    13.  
    14.     private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
    15.     void Start()
    16.     {
    17.         argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
    18.         UpdateBuffers();
    19.         instanceCount = 1000;?????????
    20.  
    21.     }
    22.  
    23.     void Update()
    24.     {
    25.         // Update starting position buffer
    26.         if (cachedInstanceCount != instanceCount) UpdateBuffers();
    27.  
    28.         // Pad input
    29.         if (Input.GetAxisRaw("Horizontal") != 0.0f) instanceCount = (int)Mathf.Clamp(instanceCount + Input.GetAxis("Horizontal") * 40000, 1.0f, 5000000.0f);
    30.         // Render
    31.       //  instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
    32.         Graphics.DrawMeshInstancedIndirect(instanceMesh, 0, instanceMaterial, new Bounds(Vector3.zero, new Vector3(100.0f, 100.0f, 100.0f)), argsBuffer);
    33.     }
    34.     void OnGUI()
    35.     {
    36.         GUI.Label(new Rect(265, 12, 200, 30), "Instance Count: " + instanceCount.ToString("N0"));
    37.         instanceCount = (int)GUI.HorizontalSlider(new Rect(25, 20, 200, 30), (float)instanceCount, 1.0f, 5000000.0f);
    38.     }
    39.     void UpdateBuffers()
    40.     {
    41.         if ( instanceCount < 1 ) instanceCount = 1;
    42.  
    43.         // Positions & Colors
    44.         if (positionBuffer != null) positionBuffer.Release();
    45.         if (colorBuffer != null) colorBuffer.Release();
    46.  
    47.         positionBuffer    = new ComputeBuffer(instanceCount, 16);
    48.         colorBuffer        = new ComputeBuffer(instanceCount, 4*4);
    49.  
    50.         Vector4[] positions = new Vector4[instanceCount];
    51.         Vector4[] colors    = new Vector4[instanceCount];
    52.  
    53.         for (int i=0; i < instanceCount; i++)
    54.         {
    55.             float angle = Random.Range(0.0f, Mathf.PI * 2.0f);
    56.             float distance = Random.Range(20.0f, 100.0f);
    57.             float height = Random.Range(-2.0f, 2.0f);
    58.             float size = Random.Range(0.05f, 0.25f);
    59.             positions[i]    = new Vector4(Mathf.Sin(angle) * distance, height, Mathf.Cos(angle) * distance, size);
    60.             colors[i]        = new Vector4( Random.value, Random.value, Random.value, 1f );
    61.         }
    62.  
    63.         positionBuffer.SetData(positions);
    64.         colorBuffer.SetData(colors);
    65.  
    66.         instanceMaterial.SetBuffer("positionBuffer", positionBuffer);
    67.         instanceMaterial.SetBuffer("colorBuffer", colorBuffer);
    68.  
    69.         // indirect args
    70.         uint numIndices = (instanceMesh != null) ? (uint)instanceMesh.GetIndexCount(0) : 0;
    71.         args[0] = numIndices;
    72.         args[1] = (uint)instanceCount;
    73.         argsBuffer.SetData(args);
    74.         cachedInstanceCount = instanceCount;
    75.     }
    76.  
    77.     void OnDisable()
    78.     {
    79.         if (positionBuffer != null) positionBuffer.Release();
    80.         positionBuffer = null;
    81.  
    82.         if (colorBuffer != null) colorBuffer.Release();
    83.         colorBuffer = null;
    84.  
    85.         if (argsBuffer != null) argsBuffer.Release();
    86.         argsBuffer = null;
    87.     }
    88. }
    89.  
    And the shader code
    Code (CSharp):
    1. Shader "Instanced/InstancedIndirectSurfaceShader"
    2. {
    3.     Properties{
    4.         _MainTex("Albedo (RGB)", 2D) = "white" {}
    5.     _Glossiness("Smoothness", Range(0,1)) = 0.5
    6.         _Metallic("Metallic", Range(0,1)) = 0.0
    7.     }
    8.         SubShader{
    9.         Tags{ "RenderType" = "Opaque" }
    10.         LOD 200
    11.  
    12.         CGPROGRAM
    13.         // Physically based Standard lighting model
    14. #pragma surface surf Standard addshadow
    15. #pragma multi_compile_instancing
    16. #pragma instancing_options procedural:setup
    17.  
    18.         sampler2D _MainTex;
    19.  
    20.     struct Input {
    21.         float2 uv_MainTex;
    22.     };
    23.  
    24. #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    25.     StructuredBuffer<float4> positionBuffer;
    26.     StructuredBuffer<float4> colorBuffer;
    27. #endif
    28.  
    29.  
    30.     void rotate2D(inout float2 v, float r)
    31.     {
    32.         float s, c;
    33.         sincos(r, s, c);
    34.         v = float2(v.x * c - v.y * s, v.x * s + v.y * c);
    35.     }
    36.  
    37.     void setup()
    38.     {
    39. #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    40.         float4 data = positionBuffer[unity_InstanceID];
    41.  
    42.         float rotation = data.w * data.w * _Time.y * 0.5f;
    43.         rotate2D(data.xz, rotation);
    44.  
    45.         unity_ObjectToWorld._11_21_31_41 = float4(data.w, 0, 0, 0);
    46.         unity_ObjectToWorld._12_22_32_42 = float4(0, data.w, 0, 0);
    47.         unity_ObjectToWorld._13_23_33_43 = float4(0, 0, data.w, 0);
    48.         unity_ObjectToWorld._14_24_34_44 = float4(data.xyz, 1);
    49.         unity_WorldToObject = unity_ObjectToWorld;
    50.         unity_WorldToObject._14_24_34 *= -1;
    51.         unity_WorldToObject._11_22_33 = 1.0f / unity_WorldToObject._11_22_33;
    52. #endif
    53.     }
    54.  
    55.     half _Glossiness;
    56.     half _Metallic;
    57.  
    58.     void surf(Input IN, inout SurfaceOutputStandard o)
    59.     {
    60.         float4 col = 1.0f;
    61.  
    62. #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED
    63.         //col.gb = (float)(unity_InstanceID % 256) / 255.0f;
    64.         col = colorBuffer[unity_InstanceID];
    65. #else
    66.         //col.gb = float4(0, 0, 1, 1);
    67.         col = float4(0, 0, 1, 1);
    68. #endif
    69.  
    70.  
    71.  
    72.         fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * col;
    73.         o.Albedo = c.rgb;
    74.         o.Metallic = _Metallic;
    75.         o.Smoothness = _Glossiness;
    76.         o.Alpha = c.a;
    77.     }
    78.     ENDCG
    79.     }
    80.         FallBack "Diffuse"
    81. }
    I tried using this shader with my example but the fps remained at 5.
    I know its a lot to ask but if someone is feeling helpful if you could explain a couple of these thing to me in layman terms if possible.
    Thx in advance
     
    Last edited: May 3, 2018
  2. LennartJohansen

    LennartJohansen

    Joined:
    Dec 1, 2014
    Posts:
    2,394
    Hi.

    There is 2 ways to do instancing with the Unity API.

    Graphics.DrawMeshInstanced and Graphics.DrawMeshInstancedIndirect

    Common for both of these is that you need to call the functions every frame. The meshes are not saved between frames. This is good since you probably want to change the selection of meshes to render based on the position/orientation of the camers.

    In your first example you call Graphics.DrawMesh where you need to send each mesh to rendering manually. This is slow.

    A first step would be to use DrawMeshInstanced.

    With this API you can render up to 1023 meshes for each call. You can send a list or array with Matrix4x4 structs that holds the position, scale and rotation for each of these meshes. You make them with Matrix4x4.TRS function.

    Another option if you have a lot of meshes is to use drawmeshinstancedIndirect. This has no 1023 limit per call but requires a lot more setup and a custom shader.

    What you do here is make a compute buffer that holds the mesh position info. and you call a Setup function in the shader where you set the ObjectToWorld and WorldToObject matrix for the instance.
    This is what your 2nd example does.
     
  3. tawdry

    tawdry

    Joined:
    Sep 3, 2014
    Posts:
    1,356
    Hi Lennart thx for the info think i will try the DrawmeshInstanced option first.
    Don't know anything about the Matrix4x4.TRS u mentioned will read up on it.
    Think maybe layman is a bit to advanced for me should have asked to explain to me as if im a toddler:).
    Any clue why the drawmeshinstancedIndirect only works with unity's meshes cube,capsule etc?