Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Is wireframe effect possible without geometry shader?

Discussion in 'Shaders' started by bitinn, Nov 24, 2016.

  1. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    I am trying to write a shader that highlight the edge between each pair of vertices, much like the UCLA Wireframe Shader.



    All implementations I came across require geometry shader support. I am wondering if this is possible with just vertex and fragment shaders?

    (Of course, I can use texture to workaround it; but I am trying to avoid create custom textures for each model that I need this effect. On the other hand, I could just use geometry shader but it presents quite a problem when porting to iOS, which lacks support.)

    I have also asked this on gamedev.stackexchange.com
     
  2. tsangwailam

    tsangwailam

    Joined:
    Jul 30, 2013
    Posts:
    280
    You can using barycentric coordinates data for creating the effect and don't need geometry shader but need adding the barycentric data in the mesh. You can read the theory here.

    http://codeflow.org/entries/2012/aug/02/easy-wireframe-display-with-barycentric-coordinates/

    I have made one selling in asset store.


    https://www.assetstore.unity3d.com/en/#!/content/68384

    And more alternative you can found in the stoe.

    https://www.assetstore.unity3d.com/en/#!/content/18794
    https://www.assetstore.unity3d.com/en/#!/content/67386



    Alternatively, I have create a shader which using grid to mimic the wireframe effect (Under review in assets store). This one is drawing a grid on the surface with fix spacing. No geometry shader and no need to edit the mesh.

    http://www.digicrafts.com.hk/unity3d/easy-wireframe-grid

    The principal is getting the vertex position and divide it to fixed spacing value. So, you can get a checker liked uv. And use the color different to draw the wireframe.

     
    Last edited: Nov 24, 2016
    bitinn likes this.
  3. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    Interesting, about barycentric data, I wonder if it's possible to set them on uv2 instead of color32?

    I am using color32 for vertex coloring, so it's quite important for me to preserve them.
     
  4. tsangwailam

    tsangwailam

    Joined:
    Jul 30, 2013
    Posts:
    280
    Yes, any uv or color slots should be ok. You may use uv4 if you have other uv.
     
  5. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Code (CSharp):
    1. Shader "Custom/WireframeSeeThrough_Geom"
    2. {
    3.     Properties
    4.     {
    5.         _WireColor("Wireframe Color", Color) = (1,1,1,1)
    6.         _WireWidth("Wireframe Width", Float) = 1.0
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.         Conservative True
    13.  
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.             #pragma geometry geom
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata
    24.             {
    25.                 float4 vertex : POSITION;
    26.             };
    27.  
    28.             struct v2g
    29.             {
    30.                 float4 vertex : SV_POSITION;
    31.             };
    32.  
    33.             float4 _WireColor;
    34.             float _WireWidth;
    35.  
    36.             appdata vert (appdata v)
    37.             {
    38.                 appdata o;
    39.                 o.vertex = UnityObjectToClipPos(v.vertex);
    40.                 return o;
    41.             }
    42.  
    43.             [maxvertexcount(18)]
    44.             void geom(triangle appdata vertices[3], inout TriangleStream<v2g> triStream)
    45.             {
    46.                 v2g OUT;
    47.                 float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
    48.                 faceNormal = normalize(faceNormal);
    49.  
    50.                 // Expand each edge into two quads
    51.                 for (int i = 0; i < 3; ++i)
    52.                 {
    53.                     int next = (i + 1) % 3;
    54.                     float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
    55.                     float4 normal = float4(faceNormal, 0);
    56.                     float4 offset = float4(_WireWidth * normalize(cross(normal.xyz, dir.xyz)),0);
    57.  
    58.                     float4 p1 = vertices[i].vertex - offset;
    59.                     float4 p2 = vertices[i].vertex + offset;
    60.                     float4 p3 = vertices[next].vertex + offset;
    61.                     float4 p4 = vertices[next].vertex - offset;
    62.  
    63.                     // First Quad
    64.                     OUT.vertex = p1; triStream.Append(OUT);
    65.                     OUT.vertex = p2; triStream.Append(OUT);
    66.                     OUT.vertex = p3; triStream.Append(OUT);
    67.  
    68.                     // Second Quad
    69.                     OUT.vertex = p1; triStream.Append(OUT);
    70.                     OUT.vertex = p3; triStream.Append(OUT);
    71.                     OUT.vertex = p4; triStream.Append(OUT);
    72.                 }
    73.             }
    74.  
    75.             float4 frag (v2g f) : SV_Target
    76.             {
    77.                 return _WireColor;
    78.             }
    79.             ENDCG
    80.         }
    81.     }
    82.     FallBack "Diffuse"
    83. }
    84.  

    Code (CSharp):
    1. Shader "Custom/WireframeBackfaceCull_Geom"
    2. {
    3.     Properties
    4.     {
    5.         _WireColor("Wireframe Color", Color) = (1,1,1,1)
    6.         _WireWidth("Wireframe Width", Float) = 1.0
    7.     }
    8.     SubShader
    9.     {
    10.         Tags { "RenderType"="Opaque" }
    11.         LOD 100
    12.         Conservative True
    13.  
    14.         Pass
    15.         {
    16.             CGPROGRAM
    17.             #pragma vertex vert
    18.             #pragma fragment frag
    19.             #pragma geometry geom
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct appdata{
    24.                 float4 vertex : POSITION;
    25.             };
    26.  
    27.             struct v2g{
    28.                 float4 vertex : SV_POSITION;
    29.                 float3 world : TEXCOORD0;
    30.             };
    31.  
    32.             float4 _WireColor;
    33.             float _WireWidth;
    34.  
    35.             v2g vert (appdata v){
    36.                 v2g o;
    37.                 o.vertex = UnityObjectToClipPos(v.vertex);
    38.                 o.world = mul(unity_ObjectToWorld, v.vertex);
    39.                 return o;
    40.             }
    41.  
    42.             float isTriFacingCamera(v2g vertices[3]){
    43.                 // Calculate the normal of the triangle
    44.                 float3 v1 = vertices[0].world.xyz;
    45.                 float3 v2 = vertices[1].world.xyz;
    46.                 float3 v3 = vertices[2].world.xyz;
    47.                 float3 normal = cross(v2 - v1, v3 - v1);
    48.                 // Check if the triangle is facing the camera
    49.                 float3 viewDirection = normalize(_WorldSpaceCameraPos - v1);
    50.                 return dot(normal, viewDirection);
    51.             }
    52.  
    53.             [maxvertexcount(18)]
    54.             void geom(triangle v2g vertices[3], inout TriangleStream<v2g> triStream){
    55.                 v2g OUT;
    56.                 if(isTriFacingCamera(vertices) < 0){return;}
    57.  
    58.                 float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
    59.                 faceNormal = normalize(faceNormal);
    60.  
    61.                 // Expand each edge into two quads
    62.                 for (int i = 0; i < 3; ++i){
    63.                     int next = (i + 1) % 3;
    64.                     float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
    65.                     float4 offset = float4(_WireWidth * normalize(cross(faceNormal, dir.xyz)),0);
    66.  
    67.                     float4 p1 = vertices[i].vertex - offset;
    68.                     float4 p2 = vertices[i].vertex + offset;
    69.                     float4 p3 = vertices[next].vertex + offset;
    70.                     float4 p4 = vertices[next].vertex - offset;
    71.  
    72.                     // First Quad
    73.                     OUT.world = float3(0,0,0);
    74.                     OUT.vertex = p1; triStream.Append(OUT);
    75.                     OUT.vertex = p2; triStream.Append(OUT);
    76.                     OUT.vertex = p3; triStream.Append(OUT);
    77.  
    78.                     // Second Quad
    79.                     OUT.vertex = p1; triStream.Append(OUT);
    80.                     OUT.vertex = p3; triStream.Append(OUT);
    81.                     OUT.vertex = p4; triStream.Append(OUT);
    82.                 }
    83.             }
    84.  
    85.             float4 frag (v2g f) : SV_Target{
    86.                 return _WireColor;
    87.             }
    88.             ENDCG
    89.         }
    90.     }
    91.     FallBack "Diffuse"
    92. }

    Code (CSharp):
    1. Shader "Custom/WireframeWithTexture"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("MainTexture", 2D) = "white"{}
    6.         _WireColor("Wireframe Color", Color) = (1,1,1,1)
    7.         _WireWidth("Wireframe Width", Range(0.001, 0.1)) = 0.05
    8.     }
    9.     SubShader
    10.     {
    11.         Tags { "RenderType"="Opaque" }
    12.         LOD 100
    13.         Conservative True
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.             #pragma geometry geom
    21.  
    22.             #include "UnityCG.cginc"
    23.  
    24.             struct appdata{
    25.                 float4 vertex : POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.             };
    28.  
    29.             struct v2g{
    30.                 float4 vertex : SV_POSITION;
    31.                 float2 uv : TEXCOORD0;
    32.                 float3 world : TEXCOORD1;
    33.             };
    34.  
    35.             sampler2D _MainTex;
    36.             float4 _WireColor;
    37.             float _WireWidth;
    38.  
    39.             v2g vert (appdata v){
    40.                 v2g o;
    41.                 o.vertex = UnityObjectToClipPos(v.vertex);
    42.                 o.world = mul(unity_ObjectToWorld, v.vertex);
    43.                 o.uv = v.uv;
    44.                 return o;
    45.             }
    46.  
    47.             float isTriFacingCamera(v2g vertices[3]){
    48.                 // Calculate the normal of the triangle
    49.                 float3 v1 = vertices[0].world.xyz;
    50.                 float3 v2 = vertices[1].world.xyz;
    51.                 float3 v3 = vertices[2].world.xyz;
    52.                 float3 normal = cross(v2 - v1, v3 - v1);
    53.                 // Check if the triangle is facing the camera
    54.                 float3 viewDirection = normalize(_WorldSpaceCameraPos - v1);
    55.                 return dot(normal, viewDirection);
    56.             }
    57.  
    58.             [maxvertexcount(21)]
    59.             void geom(triangle v2g vertices[3], inout TriangleStream<v2g> triStream){
    60.                 v2g OUT;
    61.  
    62.                 //main triangle, that represents the geometry:
    63.                 OUT.world = float3(1,1,1);
    64.                 OUT.vertex = vertices[0].vertex;
    65.                 OUT.uv = vertices[0].uv;
    66.                 triStream.Append(OUT);
    67.  
    68.                 OUT.vertex = vertices[1].vertex;
    69.                 OUT.uv = vertices[1].uv;
    70.                 triStream.Append(OUT);
    71.  
    72.                 OUT.vertex = vertices[2].vertex;
    73.                 OUT.uv = vertices[2].uv;
    74.                 triStream.Append(OUT);
    75.  
    76.                 triStream.RestartStrip();
    77.  
    78.                 if(isTriFacingCamera(vertices) < 0){return;}
    79.  
    80.                 //wireframe as 2 quads, three times:
    81.  
    82.                 float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
    83.                 faceNormal = normalize(faceNormal);
    84.  
    85.                 float4 zOffset =  float4(0,0,0.00001,0);
    86.  
    87.                 // Expand each edge into two quads
    88.                 for (int i = 0; i < 3; ++i){
    89.                     int next = (i + 1) % 3;
    90.                     float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
    91.                     float4 offset = float4(_WireWidth * normalize(cross(faceNormal, dir.xyz)),0);
    92.  
    93.                     float4 p1 = vertices[i].vertex - offset + zOffset;
    94.                     float4 p2 = vertices[i].vertex + offset + zOffset;
    95.                     float4 p3 = vertices[next].vertex + offset + zOffset;
    96.                     float4 p4 = vertices[next].vertex - offset + zOffset;
    97.  
    98.                     // First Quad
    99.                     OUT.world = float3(0,0,0);
    100.                     OUT.vertex = p1; triStream.Append(OUT);
    101.                     OUT.vertex = p2; triStream.Append(OUT);
    102.                     OUT.vertex = p3; triStream.Append(OUT);
    103.  
    104.                     // Second Quad
    105.                     OUT.vertex = p1; triStream.Append(OUT);
    106.                     OUT.vertex = p3; triStream.Append(OUT);
    107.                     OUT.vertex = p4; triStream.Append(OUT);
    108.                 }
    109.             }
    110.  
    111.             float4 frag (v2g f) : SV_Target{
    112.                 fixed4 col = tex2D(_MainTex, f.uv);
    113.                        col = lerp(_WireColor, col, f.world.x);
    114.                 return col;
    115.             }
    116.             ENDCG
    117.         }
    118.     }
    119.     FallBack "Diffuse"
    120. }
    121.  

    There is also SpatialMappingWireframe By Unity team themselves. It's quite nice (the best in my opinion), but I needed to adjust the frag function to use one color:
    Code (CSharp):
    1. // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
    2. // from https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/DefaultResourcesExtra/VR/Shaders/SpatialMappingWireframe.shader
    3. // This is adjusted version, allowing to tweak color of Wireframe, make it barely visible (alpha of color).
    4. // Allows for a texture.
    5. Shader "Custom/VRWireframe (Unlit Texture)"
    6. {
    7.     Properties
    8.     {
    9.         _WireThickness ("Wire Thickness", RANGE(0, 800)) = 100
    10.         _MainTex("MainTexture", 2D) = "white"{}
    11.         _WireColor("Wireframe Color", Color) = (1,1,1,1)
    12.     }
    13.  
    14.     SubShader
    15.     {
    16.         Tags { "RenderType"="Opaque" }
    17.  
    18.         Pass
    19.         {
    20.             // Wireframe shader based on the the following
    21.             // http://developer.download.nvidia.com/SDK/10/direct3d/Source/SolidWireframe/Doc/SolidWireframe.pdf
    22.  
    23.             CGPROGRAM
    24.             #pragma vertex vert
    25.             #pragma geometry geom
    26.             #pragma fragment frag
    27.  
    28.             #include "UnityCG.cginc"
    29.  
    30.             float _WireThickness;
    31.             sampler2D _MainTex;
    32.             fixed4 _WireColor;
    33.  
    34.             struct appdata
    35.             {
    36.                 float4 vertex : POSITION;
    37.                 float2 uv : TEXCOORD0;
    38.             };
    39.  
    40.             struct v2g
    41.             {
    42.                 float4 projectionSpaceVertex : SV_POSITION;
    43.                 float2 uv : TEXCOORD0;
    44.                 float4 worldSpacePosition : TEXCOORD1;
    45.             };
    46.  
    47.             struct g2f
    48.             {
    49.                 float4 projectionSpaceVertex : SV_POSITION;
    50.                 float2 uv : TEXCOORD0;
    51.                 float4 worldSpacePosition : TEXCOORD1;
    52.                 float4 dist : TEXCOORD2;
    53.             };
    54.  
    55.             v2g vert (appdata v)
    56.             {
    57.                 v2g o;
    58.                 o.projectionSpaceVertex = UnityObjectToClipPos(v.vertex);
    59.                 o.worldSpacePosition = mul(unity_ObjectToWorld, v.vertex);
    60.                 o.uv = v.uv;
    61.                 return o;
    62.             }
    63.  
    64.             [maxvertexcount(3)]
    65.             void geom(triangle v2g i[3], inout TriangleStream<g2f> triangleStream)
    66.             {
    67.                 float2 p0 = i[0].projectionSpaceVertex.xy / i[0].projectionSpaceVertex.w;
    68.                 float2 p1 = i[1].projectionSpaceVertex.xy / i[1].projectionSpaceVertex.w;
    69.                 float2 p2 = i[2].projectionSpaceVertex.xy / i[2].projectionSpaceVertex.w;
    70.  
    71.                 float2 edge0 = p2 - p1;
    72.                 float2 edge1 = p2 - p0;
    73.                 float2 edge2 = p1 - p0;
    74.  
    75.                 // To find the distance to the opposite edge, we take the
    76.                 // formula for finding the area of a triangle Area = Base/2 * Height,
    77.                 // and solve for the Height = (Area * 2)/Base.
    78.                 // We can get the area of a triangle by taking its cross product
    79.                 // divided by 2.  However we can avoid dividing our area/base by 2
    80.                 // since our cross product will already be double our area.
    81.                 float area = abs(edge1.x * edge2.y - edge1.y * edge2.x);
    82.                 float wireThickness = 800 - _WireThickness;
    83.  
    84.                 g2f o;
    85.                 o.worldSpacePosition = i[0].worldSpacePosition;
    86.                 o.projectionSpaceVertex = i[0].projectionSpaceVertex;
    87.                 o.dist.xyz = float3( (area / length(edge0)), 0.0, 0.0) * o.projectionSpaceVertex.w * wireThickness;
    88.                 o.dist.w = 1.0 / o.projectionSpaceVertex.w;
    89.                 o.uv = i[0].uv;
    90.                 triangleStream.Append(o);
    91.  
    92.                 o.worldSpacePosition = i[1].worldSpacePosition;
    93.                 o.projectionSpaceVertex = i[1].projectionSpaceVertex;
    94.                 o.dist.xyz = float3(0.0, (area / length(edge1)), 0.0) * o.projectionSpaceVertex.w * wireThickness;
    95.                 o.dist.w = 1.0 / o.projectionSpaceVertex.w;
    96.                 o.uv = i[1].uv;
    97.                 triangleStream.Append(o);
    98.  
    99.                 o.worldSpacePosition = i[2].worldSpacePosition;
    100.                 o.projectionSpaceVertex = i[2].projectionSpaceVertex;
    101.                 o.dist.xyz = float3(0.0, 0.0, (area / length(edge2))) * o.projectionSpaceVertex.w * wireThickness;
    102.                 o.dist.w = 1.0 / o.projectionSpaceVertex.w;
    103.                 o.uv = i[2].uv;
    104.                 triangleStream.Append(o);
    105.             }
    106.  
    107.             fixed4 frag (g2f i) : SV_Target{
    108.                 float minDistanceToEdge = min(i.dist[0], min(i.dist[1], i.dist[2])) * i.dist[3];
    109.  
    110.                 // Early out if we know we are not on a line segment.
    111.                 float4 texCol = tex2D(_MainTex, i.uv);
    112.  
    113.                 // Smooth our line out
    114.                 float t = exp2(-2 * minDistanceToEdge * minDistanceToEdge);
    115.                       t *= _WireColor.a;
    116.  
    117.                 fixed4 finalColor = lerp(texCol, _WireColor, t);
    118.                 finalColor.a = 1;
    119.  
    120.                 return finalColor;
    121.             }
    122.             ENDCG
    123.         }
    124.     }
    125. }
     
    Last edited: Dec 24, 2023