Does the order of sentence is important in Shader Writing?

I wrote a edge detect shader.In it I write following code:

struct v2f

{

half2 uv[9] : TEXCOORD0;

float4 vertex : SV_POSITION;

};

then the shader doesn’t work.

I didn’t realized it is a error. I just to formate my code and correct it as following:

struct v2f

{

float4 vertex : SV_POSITION;

half2 uv[9] : TEXCOORD0;

};

The shader works well.

                half2 uv[9] : TEXCOORD0;

I’m not sure this line is doing what you expect. Do you need the array and if so, what are you expecting assigning it to a single TEXCOORD to do?

This does work fine. It (should) properly expand them out to multiple TEXCOORD#. When you say it doesn’t work, what is going wrong?

1 Like

Ah cool. So it’s not doing what I’d expect :slight_smile: I hadn’t come across that usage before and wondered if GreatWall did it intentionally or not.

Shader "Test/DiagonalBlurTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            #define TAPS 9

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                half2 uv[TAPS] : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float4 _MainTex_TexelSize;

         
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                float2 uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv[0] = uv;

                for (int i=1; i<TAPS; i++)
                    o.uv[i] = uv + _MainTex_TexelSize.xy * ceil((float)i / 2) * sign(i % 2 - 0.5) * 4.0;

                return o;
            }
         
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv[0]) / (float)TAPS;

                for (int s=1; s<TAPS; s++)
                    col += tex2D(_MainTex, i.uv[s]) / (float)TAPS;

                return col;
            }
            ENDCG
        }
    }
}

I threw this together to test, and it works fine with the “bad” v2f ordering. Looking at what’s being produced in RenderDoc it’s passing each index as a separate TEXCOORD index.

Here’s what it looks like with that #define TAPS 15 (any higher causes an error as you run out of vertex outputs in Direct3D).
3326763--259188--UncompactedTexcoords.png

You can see TEXCOORD indices 0-14 are a float2 (note: on desktop generally half2 == float2) with the BA components unused. I was originally wondering if Unity was doing some kind of semantic compaction for vertex fragment shaders. I know it does that for Surface Shaders but I didn’t think it did for vertex fragment shaders, and my assumption appears to be correct. Otherwise a possible explanation would have been the SV_POSITION was getting improperly spanned across the last TEXCOORD semantic and the SV_POSITION.

Shader “GW/EdgeDetectionShader”
{
Properties
{
_MainTex (“Texture”, 2D) = “white” {}
_EdgeOnly (“Edge Only”,Float) = 1.0
_EdgeColor(“Edge Color”,Color) = (0,0,0,1)
_BackgroundColor(“Background Color”,Color) = (1,1,1,1)
}
SubShader
{
Pass
{
ZTest Always Cull Off ZWrite Off
CGPROGRAM
#include “UnityCG.cginc”
#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;
half4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed4 _EdgeColor;
fixed4 _BackgroundColor;
struct v2f
{
float4 vertex : SV_POSITION;
half2 uv[9] : TEXCOORD0;
};
fixed luminance(fixed4 color){
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
half Sobel(v2f i){
const half Gx[9] = {-1,-2,-1,
0,0,0,
1,2,1};
const half Gy[9] = {-1,0,1,
-2,0,2,
-1,0,1};
half texColor;
half edgeX = 0;
half edgeY = 0;
for(int it = 0;it < 9;it ++){
texColor = luminance(tex2D(_MainTex,i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
half edge = 1 - abs(edgeX) - abs(edgeY);
return edge;
}
half Prewitt(v2f i){
const half Gx[9] = {-1,-1,-1,
0,0,0,
1,1,1};
const half Gy[9] = {-1,0,1,
-1,0,1,
-1,0,1};
half texColor;
half edgeX = 0;
half edgeY = 0;
for(int it = 0;it<9;it ++){
texColor = luminance(tex2D(_MainTex,i.uv[it]));
edgeX += texColor * Gx[it];
edgeY += texColor * Gy[it];
}
half edge = 1 - (abs(edgeX) + abs(edgeY));
return edge;
}
v2f vert (appdata_img v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1,-1);
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0,-1);
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1,-1);
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,0);
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0,0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1,0);
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1,1);
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0,1);
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1,1);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// half edge = Sobel(i);
half edge = Prewitt(i);
fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[4]),edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge);
return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly);
}

ENDCG
}
}
FallBack Off
}
This is the shader, a simple edge detecting. “It doesn’t work” means the image not change,the edge not render with defined color.

yeah, intentionally. While I can’t remember learning form whom. I always understand that the TEXCOORD is an allocated memory which arranged by uv coordinates. And each unit in the memory can store something not only the texture color. So I understand that each unit in TEXCOORD0 storing an array half2 uv[9].Is it right?

yes,It is very possible.

Not really since that’s not what I’m seeing happen. I’m curious if the shader I posted works for you?

Honestly it’s kind of moot since you probably shouldn’t be doing what you’re doing. Passing calculating the UVs in the vertex shader and passing them them as a bunch of half2 values to the fragment shader was good practice for OpenGL ES 2.0, and bad for pretty much everything else.

I can confirm the buggy behaviour from changing the order. The only suspicious change I see in the compiled shader code is this:

139c139
< u_xlat10_1 = texture(_MainTex, phase0_Input0_0[0].xy);

u_xlat10_1 = texture(_MainTex, phase0_Input0_1[u_xlati_loop_1].xy);

So instead of using the loop variable ‘it’, entry 0 is always used. Just changing the order as you described causes this change from the broken to the working behaviour.

for(int u_xlati_loop_1 = 0 ; u_xlati_loop_1<9 ; u_xlati_loop_1++)
{
u_xlat10_4.xyz = texture(_MainTex, phase0_Input0_1[u_xlati_loop_1].xy).xyz;
u_xlat16_6 = dot(u_xlat10_4.xyz, vec3(0.212500006, 0.715399981, 0.0720999986));
u_xlat16_0.x = u_xlat16_6 * ImmCB_0_0_0[u_xlati_loop_1] + u_xlat16_0.x;
u_xlat16_3 = u_xlat16_6 * ImmCB_0_0_1[u_xlati_loop_1] + u_xlat16_3;
}

for(int u_xlati_loop_1 = 0 ; u_xlati_loop_1<9 ; u_xlati_loop_1++)
{
u_xlat10_4.xyz = texture(_MainTex, phase0_Input0_0[0].xy).xyz;
u_xlat16_6 = dot(u_xlat10_4.xyz, vec3(0.212500006, 0.715399981, 0.0720999986));
u_xlat16_0.x = u_xlat16_6 * ImmCB_0_0_0[u_xlati_loop_1] + u_xlat16_0.x;
u_xlat16_3 = u_xlat16_6 * ImmCB_0_0_1[u_xlati_loop_1] + u_xlat16_3;
}
It seems that the error one sample the texture only use same uv, which lead to an error in convolution.
However it is only Unity guy can tell us why shader be compiled in this way.

“Help → Report a Bug” in the editor seems the best option to me, I can’t imagine this behaviour is intentional.

I’ve also tested @bgolus blur script and get the same problem there - it fails to work and has a zero instead of the loop variable in the compiled code:

u_xlat10_3 = texture(_MainTex, phase0_Input0_0[0].xy);

Switching the order of the declarations in v2f makes the variable show up and the blur work:

u_xlat10_3 = texture(_MainTex, phase0_Input0_1[u_xlati_loop_1].xy);

Just in case it’s a platform or version specific issue - I’m using Unity 2017.1.2f1 on Linux (OpenGL 4.5).

Edit: Oldest version I have installed is 5.5.3xf1 and that shows the same issue.

Edit2: I guess we can rule out it being version specific. Just installed 2017.3.0f1 and have the same behaviour.

Tried, same here, 5.6.1, 2017.1.0, 2017.1.1, 2017.2 and 2017.3

Wait, for the people for whom the shader doesn’t work, are you all using OpenGL (ie: Linux / Mac)? I’m running Windows 10 and it works for me.

* switches editor to OpenGL

Yep, it’s broken in OpenGL. Likely a bug somewhere in the HLSL to GLSL conversion. It should probably be reported like @DominoM suggested.

However, for this particular case you’re likely better off calculating the UV offsets in the fragment shader like this:

Shader "GW/EdgeDetectionShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _EdgeOnly ("Edge Only",Float) = 1.0
        _EdgeColor("Edge Color",Color) = (0,0,0,1)
        _BackgroundColor("Background Color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            ZTest Always Cull Off ZWrite Off
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            half4 _MainTex_TexelSize;
            fixed _EdgeOnly;
            fixed4 _EdgeColor;
            fixed4 _BackgroundColor;

            struct v2f
            {
                half2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            fixed luminance(fixed4 color){
                return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
            }

            half Sobel(half2 uvs[9]){
                const half Gx[9] = {-1,-2,-1,
                0,0,0,
                1,2,1};
                const half Gy[9] = {-1,0,1,
                -2,0,2,
                -1,0,1};
                half texColor;
                half edgeX = 0;
                half edgeY = 0;
                for(int it = 0;it < 9;it ++){
                texColor = luminance(tex2D(_MainTex,uvs[it]));
                edgeX += texColor * Gx[it];
                edgeY += texColor * Gy[it];
                }
                half edge = 1 - abs(edgeX) - abs(edgeY);
                return edge;
            }

            half Prewitt(half2 uvs[9]){
                const half Gx[9] = {-1,-1,-1,
                0,0,0,
                1,1,1};
                const half Gy[9] = {-1,0,1,
                -1,0,1,
                -1,0,1};
                half texColor;
                half edgeX = 0;
                half edgeY = 0;
                for(int it = 0;it<9;it ++){
                texColor = luminance(tex2D(_MainTex,uvs[it]));
                edgeX += texColor * Gx[it];
                edgeY += texColor * Gy[it];
                }
                half edge = 1 - (abs(edgeX) + abs(edgeY));
                return edge;
            }
         
            v2f vert (appdata_img v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                half2 uvs[9];
                uvs[0] = i.uv + _MainTex_TexelSize.xy * half2(-1,-1);
                uvs[1] = i.uv + _MainTex_TexelSize.xy * half2(0,-1);
                uvs[2] = i.uv + _MainTex_TexelSize.xy * half2(1,-1);
                uvs[3] = i.uv + _MainTex_TexelSize.xy * half2(-1,0);
                uvs[4] = i.uv + _MainTex_TexelSize.xy * half2(0,0);
                uvs[5] = i.uv + _MainTex_TexelSize.xy * half2(1,0);
                uvs[6] = i.uv + _MainTex_TexelSize.xy * half2(-1,1);
                uvs[7] = i.uv + _MainTex_TexelSize.xy * half2(0,1);
                uvs[8] = i.uv + _MainTex_TexelSize.xy * half2(1,1);

                // half edge = Sobel(uvs);
                half edge = Prewitt(uvs);
                fixed4 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,uvs[4]),edge);
                fixed4 onlyEdgeColor = lerp(_EdgeColor,_BackgroundColor,edge);
                return lerp(withEdgeColor,onlyEdgeColor,_EdgeOnly);
            }

            ENDCG
        }
    }
    FallBack Off
}

It might seem weird to do all of that extra work in the fragment shader, but the reality is it only trivially increases the cost of the fragment shader, but saves a ton on interpolation. Passing data from the vertex shader to the fragment shader is not free, and you’re passing a ton of data that is trivial to calculate.

The estimates I’ve seen from ~2012 was the cost of transferring a single float4 from vertex to fragment was about the same cost as ~2 instructions in the shader. This is trading 4~8 float4s (because DirectX 11 treats a half2 as a float4 and throws away the last two components in the fragment) for ~6 additional instructions in the fragment. On modern GCN based AMD GPUs it might even be a better ratio since the interpolation is done in the fragment shader. On mobile GPUs have really low memory bandwidth which is where most of the cost of the vertex to fragment shader is, so it should be even better there too.

1 Like

A bug report has been sent.Let’s wait for Unity’s response.Thanks for your tests @[Daemonhahn]( https://discussions.unity.com/t/685950 members/daemonhahn.424621/) @[DominoM]( https://discussions.unity.com/t/685950 members/dominom.1215728/).And indeed I use OpenGL @[bgolus]( https://discussions.unity.com/t/685950 members/bgolus.163285/).

Thanks you for your suggestion.I didn’t know that before; It is very helpful.