Search Unity

Using Instanced or Indirect Properties in a Vertex Shader

Discussion in 'Shaders' started by 00jknight, Jun 18, 2018.

  1. 00jknight

    00jknight

    Joined:
    Mar 28, 2014
    Posts:
    34
    Hi there,

    I am trying to draw lines using 2 structured buffers:

    StructuredBuffer<float3> positions;
    StructuredBuffer<float3> vectors;


    This is easily accomplished in a surface shader by using the "setup" method and then positioning and scaling the object using unity_InstanceID & unity_ObjectToWorld matrices.

    However I'd like to accomplish this in an unlit shader, but unity_InstanceID is not defined.

    Looking at UNITY_VERTEX_INPUT_INSTANCE_ID in UnityInstancing.cginc, I should be able to do v.instanceID, but that's not accessible either.

    How can I position a vertex using data from a StructuredBuffer?

    OR

    How can I position a vertex using data from a "UNITY_DEFINE_INSTANCED_PROP", also what's the difference between a StructuredBuffer and a buffer defined using "UNITY_DEFINE_INSTANCED_PROP"?
     
  2. 00jknight

    00jknight

    Joined:
    Mar 28, 2014
    Posts:
    34
    Here's the code, line 43 "float3 position = positions[v.instanceID];" fails on "invalid subscript 'instanceID'":


    Code (CSharp):
    1. Shader "Instanced/InstancedLineUnlitShader"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (1, 1, 1, 1)
    6.     }
    7.  
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.  
    13.         Pass
    14.         {
    15.             CGPROGRAM
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.             #pragma multi_compile_instancing
    19.             #include "UnityCG.cginc"
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float4 vertex : SV_POSITION;
    30.             };
    31.  
    32.             float4 _Color;
    33.  
    34.            
    35.             StructuredBuffer<float3> positions;
    36.             StructuredBuffer<float3> vectors;
    37.            
    38.             v2f vert(appdata v)
    39.             {
    40.                 v2f o;
    41.  
    42.                 UNITY_SETUP_INSTANCE_ID(v);
    43.                 float3 position = positions[v.instanceID];
    44.                 float3 vect = vectors[v.instanceID];
    45.                   o.vertex = UnityObjectToClipPos(position + mul(v.vertex, vect));
    46.                 return o;
    47.             }
    48.          
    49.             fixed4 frag(v2f i) : SV_Target
    50.             {
    51.                 return _Color;
    52.             }
    53.             ENDCG
    54.         }
    55.     }
    56. }
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The instanceID will only exist for the variants of the shader that have instancing enabled, and #pragma multi_compile_instancing will still produce non-instanced variants as a backup. You need to test if the current variant actually has instancing enabled. Also, you should probably not directly use v.instanceID, but rather unity_InstanceID which is set by the UNITY_SETUP_INSTANCE_ID macro.

    Unity uses this #if to know whether or not to pass the instance ID and to create the unity_InstanceID value:

    #if defined(UNITY_INSTANCING_ENABLED) || defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) || defined(UNITY_STEREO_INSTANCING_ENABLED)

    And you could only access the structs when that is true, and otherwise define default values for position and vect. However you could also add a line like this:

    UNITY_SETUP_INSTANCE_ID(v);
    #if !defined(unity_InstanceID)
    uint unity_InstanceID = 0;
    #endif

    float3 position = positions[unity_InstanceID];
    float3 vect = vectors[unity_InstanceID];
     
    burakkurkcu likes this.
  4. 00jknight

    00jknight

    Joined:
    Mar 28, 2014
    Posts:
    34
    No variants of the vertex shader are ever compiled with UNITY_INSTANCING_ENABLED or UNITY_PROCEDURAL_INSTANCING_ENABLED or UNITY_STEREO_INSTANCING_ENABLED......

    I dont think it's possible to use the instance id in a vertex shader, perhaps it's accesible in a geometry shader?
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You need to check the "Enable GPU Instancing" check box on your material, having the #pragma multi_compile_instancing is not enough on its own to enable these variants. If you're creating the material from script you set myMaterial.enableInstancing = true to "check the box" as well.

    https://docs.unity3d.com/ScriptReference/Material-enableInstancing.html
     
  6. 00jknight

    00jknight

    Joined:
    Mar 28, 2014
    Posts:
    34
    Instancing is enabled. Have you been able to use unity_InstanceID in a vertex shader or are you just assuming that it's possible and working backwards from that assumption? I think maybe I need to add #pragma instancing_options procedural:setup...
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I'm going by that's how Unity's own shaders work, and how I've used it for my own instanced shaders.

    However I did give you some bad information, and that is that if you're using something like DrawMeshInstancedIndirect to render then you are correct that none of those defines are ever set. Looking at the example page for that function reveals a very odd thing in their vertex fragment shader example, they don't use any of the built in instancing stuff at all and just check to see if the shader target is high enough (unnecessarily since #pragma target 4.5 is set) and use the SV_InstancedID directly.

    https://docs.unity3d.com/ScriptReference/Graphics.DrawMeshInstancedIndirect.html

    Adding #pragma instancing_options procedural:setup to the shader with a dummy void setup() {} function does cause UNITY_PROCEDURAL_INSTANCING_ENABLED to be defined, and then the shader can proceed as you expect with unity_InstanceID.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    One more thing as a point of clarity. A geometry shader is a vertex fragment shader with an additional stage between the vertex stage and fragment stage. Additionally Surface Shaders are also vertex fragment shaders, or more specifically vertex fragment shader generators, the vertex fragment shader code for which you can see by selecting the shader and clicking on the Show Generated Code.
     
    Mockarutan likes this.
  9. 00jknight

    00jknight

    Joined:
    Mar 28, 2014
    Posts:
    34
    So adding #pragma instancing_options procedural:setup to the shader with a dummy void setup() {} seemed to be the only way to get this to work. I couldnt access SV_InstancedID directly.

    Thanks for your help.
     
    SLGSimon likes this.
  10. wusticality

    wusticality

    Joined:
    Dec 15, 2016
    Posts:
    71
    Could you guys share your final code?