5

I have a wireframe shader that displays triangles such as on the left cube, and wanted to update it so that it would only display quads such as on the right cube.

Triangles VS Quads

Here's the code:

Shader "Custom/Wireframe"
{
    Properties
    {
        _WireColor("WireColor", Color) = (1,0,0,1)
        _Color("Color", Color) = (1,1,1,1)
    }
SubShader
{
    Pass
    {
        CGPROGRAM
        #include "UnityCG.cginc"
        #pragma target 5.0
        #pragma vertex vert
        #pragma geometry geom
        #pragma fragment frag

        half4 _WireColor, _Color;

        struct v2g
        {
            float4  pos : SV_POSITION;
            float2  uv : TEXCOORD0;
        };

        struct g2f
        {
            float4  pos : SV_POSITION;
            float2  uv : TEXCOORD0;
            float3 dist : TEXCOORD1;
        };

        v2g vert(appdata_base v)
        {
            v2g OUT;
            OUT.pos = mul(UNITY_MATRIX_MVP, v.vertex);
            OUT.uv = v.texcoord;
            return OUT;
        }

        [maxvertexcount(3)]
        void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream)
        {

            float2 WIN_SCALE = float2(_ScreenParams.x/2.0, _ScreenParams.y/2.0);

            //frag position
            float2 p0 = WIN_SCALE * IN[0].pos.xy / IN[0].pos.w;
            float2 p1 = WIN_SCALE * IN[1].pos.xy / IN[1].pos.w;
            float2 p2 = WIN_SCALE * IN[2].pos.xy / IN[2].pos.w;

            //barycentric position
            float2 v0 = p2-p1;
            float2 v1 = p2-p0;
            float2 v2 = p1-p0;
            //triangles area
            float area = abs(v1.x*v2.y - v1.y * v2.x);

            g2f OUT;
            OUT.pos = IN[0].pos;
            OUT.uv = IN[0].uv;
            OUT.dist = float3(area/length(v0),0,0);
            triStream.Append(OUT);

            OUT.pos = IN[1].pos;
            OUT.uv = IN[1].uv;
            OUT.dist = float3(0,area/length(v1),0);
            triStream.Append(OUT);

            OUT.pos = IN[2].pos;
            OUT.uv = IN[2].uv;
            OUT.dist = float3(0,0,area/length(v2));
            triStream.Append(OUT);

        }

        half4 frag(g2f IN) : COLOR
        {
            //distance of frag from triangles center
            float d = min(IN.dist.x, min(IN.dist.y, IN.dist.z));
            //fade based on dist from center
             float I = exp2(-4.0*d*d);

             return lerp(_Color, _WireColor, I);              
        }
        ENDCG
    }
}

Someone has mentioned that one way to do it would be to compare the normals of the neighboring triangles. If the dot product of the two normals is close to 1, the edge can be skipped. But I don't know how to implement it as I don't have any knowledge on geometry shaders.

Can you please help me edit this shader?

Thanks.

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
user1546493
  • 133
  • 1
  • 1
  • 6
  • This is not an easy problem to solve, assuming it is solvable at all. For example, if you rendered only the shortest 2 edges of any given triangle (that is, making the assumption that the diagonal across a quad is longer than the two sides), how would you expect [this quad](https://i.stack.imgur.com/Dt6UT.png) to render? – Draco18s no longer trusts SE Mar 18 '18 at 18:09
  • Actually this solution might work on my project. Can you please tell me how to implement it in the shader? I want to test it. – user1546493 Mar 18 '18 at 18:52
  • I vaguely recall attempting something similar a few years ago, but I don't remember what I did and never kept that particular attempt. – Draco18s no longer trusts SE Mar 19 '18 at 03:18

2 Answers2

3

Someone posted a question where one of the pieces that they started with was...a diagonal-less wireframe shader! Being on the lookout for one of these for some time (and remembering this question) I figured it deserved an answer.

The shader below was found at this site.

Shader "Custom/Geometry/Wireframe"
{
    Properties
    {
        [PowerSlider(3.0)]
        _WireframeVal ("Wireframe width", Range(0., 0.5)) = 0.05
        _FrontColor ("Front color", color) = (1., 1., 1., 1.)
        _BackColor ("Back color", color) = (1., 1., 1., 1.)
        [Toggle] _RemoveDiag("Remove diagonals?", Float) = 0.
    }
    SubShader
    {
        Tags { "Queue"="Geometry" "RenderType"="Opaque" }

        Pass
        {
            Cull Front
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            // Change "shader_feature" with "pragma_compile" if you want set this keyword from c# code
            #pragma shader_feature __ _REMOVEDIAG_ON

            #include "UnityCG.cginc"

            struct v2g {
                float4 worldPos : SV_POSITION;
            };

            struct g2f {
                float4 pos : SV_POSITION;
                float3 bary : TEXCOORD0;
            };

            v2g vert(appdata_base v) {
                v2g o;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            [maxvertexcount(3)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream) {
                float3 param = float3(0., 0., 0.);

                #if _REMOVEDIAG_ON
                float EdgeA = length(IN[0].worldPos - IN[1].worldPos);
                float EdgeB = length(IN[1].worldPos - IN[2].worldPos);
                float EdgeC = length(IN[2].worldPos - IN[0].worldPos);

                if(EdgeA > EdgeB && EdgeA > EdgeC)
                    param.y = 1.;
                else if (EdgeB > EdgeC && EdgeB > EdgeA)
                    param.x = 1.;
                else
                    param.z = 1.;
                #endif

                g2f o;
                o.pos = mul(UNITY_MATRIX_VP, IN[0].worldPos);
                o.bary = float3(1., 0., 0.) + param;
                triStream.Append(o);
                o.pos = mul(UNITY_MATRIX_VP, IN[1].worldPos);
                o.bary = float3(0., 0., 1.) + param;
                triStream.Append(o);
                o.pos = mul(UNITY_MATRIX_VP, IN[2].worldPos);
                o.bary = float3(0., 1., 0.) + param;
                triStream.Append(o);
            }

            float _WireframeVal;
            fixed4 _BackColor;

            fixed4 frag(g2f i) : SV_Target {
            if(!any(bool3(i.bary.x < _WireframeVal, i.bary.y < _WireframeVal, i.bary.z < _WireframeVal)))
                 discard;

                return _BackColor;
            }

            ENDCG
        }

        Pass
        {
            Cull Back
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            // Change "shader_feature" with "pragma_compile" if you want set this keyword from c# code
            #pragma shader_feature __ _REMOVEDIAG_ON

            #include "UnityCG.cginc"

            struct v2g {
                float4 worldPos : SV_POSITION;
            };

            struct g2f {
                float4 pos : SV_POSITION;
                float3 bary : TEXCOORD0;
            };

            v2g vert(appdata_base v) {
                v2g o;
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            [maxvertexcount(3)]
            void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream) {
                float3 param = float3(0., 0., 0.);

                #if _REMOVEDIAG_ON
                float EdgeA = length(IN[0].worldPos - IN[1].worldPos);
                float EdgeB = length(IN[1].worldPos - IN[2].worldPos);
                float EdgeC = length(IN[2].worldPos - IN[0].worldPos);

                if(EdgeA > EdgeB && EdgeA > EdgeC)
                    param.y = 1.;
                else if (EdgeB > EdgeC && EdgeB > EdgeA)
                    param.x = 1.;
                else
                    param.z = 1.;
                #endif

                g2f o;
                o.pos = mul(UNITY_MATRIX_VP, IN[0].worldPos);
                o.bary = float3(1., 0., 0.) + param;
                triStream.Append(o);
                o.pos = mul(UNITY_MATRIX_VP, IN[1].worldPos);
                o.bary = float3(0., 0., 1.) + param;
                triStream.Append(o);
                o.pos = mul(UNITY_MATRIX_VP, IN[2].worldPos);
                o.bary = float3(0., 1., 0.) + param;
                triStream.Append(o);
            }

            float _WireframeVal;
            fixed4 _FrontColor;

            fixed4 frag(g2f i) : SV_Target {
            if(!any(bool3(i.bary.x <= _WireframeVal, i.bary.y <= _WireframeVal, i.bary.z <= _WireframeVal)))
                 discard;

                return _FrontColor;
            }

            ENDCG
        }
    }
}

Do note the #pragma shader_feature __ _REMOVEDIAG_ON lines, which will cause two shaders to get compiled internally, so if you only need it to do the One Thing, you will need to remove this line and adjust any other references appropriately.

  • Sorry to bother... is there a mobile friendly version of something like this? This seems to rely on things only in higher end PC GPUs – Confused Feb 08 '19 at 03:54
  • @Confused This is the first shader I've come across that does what the asker is looking for. Its unfortunate that it isn't mobile friendly. :( – Draco18s no longer trusts SE Feb 08 '19 at 05:09
  • Yeah. I looked and looked, found nothing. I think I might have to bake in wireframes from 3D modeller. gnaff. – Confused Feb 11 '19 at 08:14
2

Unity, and graphics engines in general (OpenGL/D3D) do not operate on quads, its an alien term to them. I am not a shader ninja who would just whip out a snippet for you, but you can try filtering your geometry by normal dicontinuity - only draw the edge between tris that have different normals (or more specifically different enough as this is float math and stuff is rarely precisely equal) - that would hide edges on surfaces that are flat.

One approach would involve sampling the depthmap, and checking if the difference between us and our neighbours is relatively constant in fragment shader.

Maybe you could pass the info via vertex colour

While not directly related to the question, reading Keijiro's code is always illuminating and he writes a bit about how he approached modifying skinned geometry. His approach involves caching normals in a 3D texture (you could also use spare UV coordinates) to expose additional geometry information to the shader.

https://github.com/keijiro/SkinnedVertexModifier

The shader cannot access any data regarding other points than itself, but if you put that data in textures or UV, you can feed it some guidance as to how to progress. in general there is know easy to way to know from a shader level how you relate to the rest of the geometry, you need to generate that data in advance on the CPU and feed it to the shader so its already computer by the time the shader starts. I hope this helps

zambari
  • 4,797
  • 1
  • 12
  • 22