Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

How to use 'inout' or have multiple passes through one texture?

Discussion in 'Shaders' started by astracat111, Sep 2, 2020.

  1. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    I have a vertex/fragment shader and I can't figure out how to do multiple passes with a single texture.

    The simple surface shader I have for my game works with multiple SubShaders, and I'm assuming it's this 'inout' keyword that's used here:

    Code (CSharp):
    1. void surf (Input IN, inout SurfaceOutput o) {
    2.                 fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    3.                 c.r = c.r * _Brightness;
    4.                 c.g = c.g * _Brightness;
    5.                 c.b = c.b * _Brightness;
    6.                 o.Albedo = c.rgb;
    7.                 o.Alpha = c.a;
    8.             }
    Well, in my fragment shader when I place inout like this:

    Code (CSharp):
    1. half4 main_fragment(inout out_vertex VAR) : COLOR
    It says that this:

    Code (CSharp):
    1.     struct out_vertex
    2.     {
    3.         float4 position : POSITION;
    4.         float4 texCoord : TEXCOORD0;
    5.         float4 t1       : TEXCOORD1;
    6.         float4 t2       : TEXCOORD2;
    7.         float4 t3       : TEXCOORD3;
    8.         float4 t4       : TEXCOORD4;
    9.         float4 t5       : TEXCOORD5;
    10.         float4 t6       : TEXCOORD6;
    11.         float4 t7       : TEXCOORD7;
    12.     };
    Isn't an 'invalid ps_4_0 output semantic' and lists the properties as each not being valid.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    Fragment shaders can't output
    POSITION
    or
    TEXCOORD
    semantics. Mainly just
    SV_Target
    (or
    COLOR
    , which is just remapped to
    SV_Target
    by the compiler).

    The
    surf
    function can do an
    inout
    because that's a function called inside a larger fragment shader function. Its output is not the output of the fragment shader stage. Also, if you have multiple passes, that
    inout
    isn't getting the previous passes' values, nor is the output being passed onto the next. It's just the values being used by that pass, and they get recalculated again for any other pass that uses it.
     
    astracat111 likes this.
  3. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    @bgolus Thanks bgolus.

    What I don't understand, then, is how to have a shader that has multiple passes.

    If you have two subshaders like this, why does the second SubShader do nothing?:

    Code (CSharp):
    1. Shader "Unlit/NewUnlitShader"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 100
    11.  
    12.         Pass
    13.         {
    14.             CGPROGRAM
    15.             #pragma vertex vert
    16.             #pragma fragment frag
    17.             // make fog work
    18.             #pragma multi_compile_fog
    19.          
    20.             #include "UnityCG.cginc"
    21.  
    22.             struct appdata
    23.             {
    24.                 float4 vertex : POSITION;
    25.                 float2 uv : TEXCOORD0;
    26.             };
    27.  
    28.             struct v2f
    29.             {
    30.                 float2 uv : TEXCOORD0;
    31.                 UNITY_FOG_COORDS(1)
    32.                 float4 vertex : SV_POSITION;
    33.             };
    34.  
    35.             sampler2D _MainTex;
    36.             float4 _MainTex_ST;
    37.          
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 o.vertex = UnityObjectToClipPos(v.vertex);
    42.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    43.                 UNITY_TRANSFER_FOG(o,o.vertex);
    44.                 return o;
    45.             }
    46.          
    47.             fixed4 frag (v2f i) : SV_Target
    48.             {
    49.                 // sample the texture
    50.                 fixed4 col = tex2D(_MainTex, i.uv);
    51.                 // apply fog
    52.                 UNITY_APPLY_FOG(i.fogCoord, col);
    53.                 col.r = 1.0;
    54.                 return col;
    55.             }
    56.             ENDCG
    57.         }
    58.     }
    59.  
    60.  
    61.     SubShader
    62.     {
    63.         Tags { "RenderType"="Opaque" }
    64.         LOD 100
    65.  
    66.         Pass
    67.         {
    68.             CGPROGRAM
    69.             #pragma vertex vert
    70.             #pragma fragment frag
    71.             // make fog work
    72.             #pragma multi_compile_fog
    73.          
    74.             #include "UnityCG.cginc"
    75.  
    76.             struct appdata
    77.             {
    78.                 float4 vertex : POSITION;
    79.                 float2 uv : TEXCOORD0;
    80.             };
    81.  
    82.             struct v2f
    83.             {
    84.                 float2 uv : TEXCOORD0;
    85.                 UNITY_FOG_COORDS(1)
    86.                 float4 vertex : SV_POSITION;
    87.             };
    88.  
    89.             sampler2D _MainTex;
    90.             float4 _MainTex_ST;
    91.          
    92.             v2f vert (appdata v)
    93.             {
    94.                 v2f o;
    95.                 o.vertex = UnityObjectToClipPos(v.vertex);
    96.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    97.                 UNITY_TRANSFER_FOG(o,o.vertex);
    98.                 return o;
    99.             }
    100.          
    101.             fixed4 frag (v2f i) : SV_Target
    102.             {
    103.                 // sample the texture
    104.                 fixed4 col = tex2D(_MainTex, i.uv);
    105.                 // apply fog
    106.                 UNITY_APPLY_FOG(i.fogCoord, col);
    107.                 col.g = 1.0;
    108.                 return col;
    109.             }
    110.             ENDCG
    111.         }
    112.     }
    113.  
    114. }
    In the fragment shaders here try to turn it to red, then the green on this part:

    Code (CSharp):
    1.  fixed4 frag (v2f i) : SV_Target
    2.             {
    3.                 // sample the texture
    4.                 fixed4 col = tex2D(_MainTex, i.uv);
    5.                 // apply fog
    6.                 UNITY_APPLY_FOG(i.fogCoord, col);
    7.                 col.g = 1.0;
    8.                 return col;
    9.             }
    The only solution I can really think of is to have multiple materials.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    Unity only uses the first
    SubShader
    that can run on the current platform it’s being run on. If you want multiple passes, just multiple passes in a single
    SubShader.


    But what exactly are you trying to do? Because in the above example, even if you do put both passes in a single sub shader, you’ll only see the “green” pass since that pass fully overwrites the pixels of the “red” pass.
     
  5. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    Well, it’s an odd problem that’ll probably be solved by modifying the texture because it gives the gpu some extra work.

    I got xbrz x6 scaling working as a shader, so the sprite is displayed all nice and smooth like this:

    Before:

    After:


    The problem is that it’s all in a vertex/fragment shader, so it's not easy to get it to receive or cast shadows.

    So, I tried to convert it into a surface shader, and while I got all the values lined up, it came up with an error because of this struct:

    Code (CSharp):
    1.     struct out_vertex
    2.     {
    3.         float4 position : POSITION;
    4.         float2 texCoord : TEXCOORD0;
    5.         float4 t1       : TEXCOORD1;
    6.         float4 t2       : TEXCOORD2;
    7.         float4 t3       : TEXCOORD3;
    8.         float4 t4       : TEXCOORD4;
    9.         float4 t5       : TEXCOORD5;
    10.         float4 t6       : TEXCOORD6;
    11.         float4 t7       : TEXCOORD7;
    12.     };
    In where it said that it has a max limit of using 10....something I'm sorry I forgot the error, but you can only have so many float4's used and Unity's extra stuff it's doing behind the scenes in a surface shader maxed it out.

    There are only two solutions I can think of, and the latter is probably the better solution I could find at this point.

    1) Somehow get a surface shader to take the data that's now there and use it, or have a second pass that takes the new scaled data and uses it (which I think people are telling me is impossible in shaders).
    2) Just pre-scale the graphics by modding this open source tool:
    https://sourceforge.net/projects/xbrz/

    I know you're gonna say that pre-scaling is better because it's more performant, but my idea was that I'd be able to have it be an instant process from Photoshop to game. I'll just have to mod that scaler I think into an editor script that searches through the character sprite sheet folders and makes sure the graphics are pre-scaled, and then use the simple cutout surface shader I was using.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,366
    Surface Shaders pass data from the vertex to the
    surf
    function via the
    Input
    struct rather than
    appdata
    . However that’s passing a lot of data between the vertex shader and fragment shader. A basic lit Unity shader already has quite a bit of data it needs to pass from the vertex shader, so realistically you can’t pass all of that data.

    Really what you’ll want to do is calculate all of those offset UVs in the
    surf
    function from the base UV rather than doing it in the vertex shader. The actual math it’s doing to calculate those UVs is super cheap, so it’s not going to make things slower. It might even be faster since transferring data between the vertex and fragment stages isn’t free. On very old GPUs there was some benefit to calculating all the UVs in the vertex shader, but that hasn’t been really true even on mobile for nearly a decade.
     
    astracat111 likes this.
  7. astracat111

    astracat111

    Joined:
    Sep 21, 2016
    Posts:
    725
    @bgolus Alright, thanks for all the help. What I'm ending up doing is pre-scaling the graphics in a scaler separate from Unity and just magnifying the textures. Right now each separate sprite for animation is 96x96 and that's typically times 8-22 per character. So it'll be 576x576 sprites with an average of 6 characters per scene...I'm thinking then 48 576x576 textures loaded into ram. I'm thinking that as long as all the textures on the trees and brush have mipmaps I should be fine.