Search Unity

Predefined custom struct from properties

Discussion in 'Shaders' started by TheCelt, Sep 11, 2019.

  1. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Hey

    Can we create structs from properties outside of shader functions ?

    For example if i had a property of a Vector type can i then apply it to my custom struct in the same scope as i declare the property for my shader:

    Property stuff:
    Code (CSharp):
    1. _V ("Vector2", Vector) = (1,1)
    In the shader code:

    Code (CSharp):
    1. //declare variables
    2. float2 _V;
    3. MyStruct myStruct; // from a cginc file
    4. myStruct.x = _V.x;
    5. myStruct.y = _V.y;
    At the moment i get unrecognised identifier for my struct.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You cannot use the = operator like that outside of a function. Only constant values can be assigned to uniforms (values declared outside of a function).

    ie: float2 _V = float2(0,1);

    That said using structs are a little weird as you cannot assign default values to elements of a struct, and I don't think Unity properly supports structs as uniforms (they're an edge case for HLSL anyway). The proper way to handle this is to assign the struct values inside a function.
     
    lGillot likes this.
  3. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    So i would have to create the struct with the data ever time a shader function is ran ? For me i wanted it as just a container to use rather than having functions with all my individual properties in the function parameters because some times they take a lot.

    An example is basically me trying to avoid this:

    Code (CSharp):
    1. // in vertex shader
    2. // this is a pain when you have longer variable names
    3. float a = SomeFunction(b,c,d,e,f,g);
    With:

    Code (CSharp):
    1.  
    2. //my struct has b,c,d,e,f,g data like a container
    3. float a = SomeFunction(myStruct);
    It seems if i want to do the struct as a container my only way is to create myStruct every time the vertex shader is called right ?

    Or is there some initialise function that only executes once per frame before the vertex shader perhaps ?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    So, I think this is the main problem you’re having here. Your expectations are a little off.

    In a compiled shader, the struct no longer exists. It’s just there to help you with organization. Also, everything you put in a shader gets called for every vertex / pixel it gets used unless you set it as a property from a C# script.

    However, the solution for what you’re doing might be solved using a macro or function that copies the values you want to the struct, then use the struct for the rest of the shader. The difference between a macro and a function is you can list out the property names in the macro so it keeps the function itself cleaner.
    Code (csharp):
    1. struct MyStruct {
    2.     float a;
    3.     float b;
    4. };
    5.  
    6. float2 _MyVal;
    7.  
    8. // multi-line macro #define
    9. #define CREATE_MYSTRUCT MyStruct myStruct; \
    10.     myStruct.a = _MyVal.x; \
    11.     myStruct.b = _MyVal.y
    12. // notice no ending semicolon, this is intentional!
    13.  
    14. v2f vert (appdata_full v)
    15. {
    16.     v2f o;
    17.  
    18.     // create and initialize myStruct
    19.     CREATE_MYSTRUCT; // this line does have a semicolon! Either the #define has one or this line does, but not both
    20.  
    21.     // now use myStruct elsewhere
    22.     SomeFunction(myStruct);
    23.  
    24.     // ...
    Additionally, having a giant set of nested function calls or struct / variable assignment has zero impact on the performance of a shader, only some on the initial build time compile of it. All the code gets distilled down to its simplest form with most kinds of indirection or nested calls getting thrown away.
     
    lGillot likes this.
  5. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Ah okay so no matter what happens, i will be creating the struct per vertex then ? I'm surprised shaders were not setup in such a way to pre-initialise data in some kinda Init() function every frame to reduce the need to do it every vertex or pixel or what ever it may be. Guessing there is a reason at the hardware level then?
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    So, if you do it in the vertex shader, and need the data in the fragment shader, you're doing the initialization in both. They're separate things that know nothing of each other apart from what interpolated values the fragment receives through the output semantics. More importantly it's happening on every vertex and every pixel because each known nothing of the other vertices or fragments.

    "Pre-initialized data" is what the material properties are for. Those set the uniform values, and are what are used by the shader. Otherwise constant values can be set either as static const globals, #define macros, or inline. The shader actually doesn't differentiate between these and in the compiled shader they're all the same thing!

    And again, the struct doesn't really exist. The individual values of the struct are treated just like any other individual values in the shader. The struct exists only in the high level code for your own code organization. Even the functions themselves don't actually exist. The math is all just inlined.
     
    TheCelt likes this.
  7. TheCelt

    TheCelt

    Joined:
    Feb 27, 2013
    Posts:
    742
    Ah i see thanks for explaining :)
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    A bit more to wrap your head around and drive this home. These examples compile to identical shaders.

    Code (csharp):
    1. float4 _MyColor;
    2. float _MyFloat;
    3.  
    4. float4 frag(v2f i) : SV_Target
    5. {
    6.     return _MyColor * _MyFloat;
    7. }
    Code (csharp):
    1. float4 _MyColor;
    2. float _MyFloat;
    3.  
    4. float4 SomeFunc(float4 col, float mul)
    5. {
    6.     return col * mul;
    7. }
    8.  
    9. float4 frag(v2f i) : SV_Target
    10. {
    11.     float4 col = SomeFunc(_MyColor, _MyFloat);
    12.  
    13.     return col;
    14. }
    Code (csharp):
    1. float4 _MyColor;
    2. float _MyFloat;
    3.  
    4. struct myStruct {
    5.     float4 col;
    6.     float mul;
    7. };
    8.  
    9. float4 SomeFunc(myStruct data)
    10. {
    11.     return data.col * data.mul;
    12. }
    13.  
    14. float4 frag(v2f i) : SV_Target
    15. {
    16.     myStruct data;
    17.     data.col = _MyColor;
    18.     data.mul = _MyFloat;
    19.  
    20.     float4 col = SomeFunc(data);
    21.  
    22.     return col;
    23. }
     
    TheCelt likes this.