Wireframe SeeThrough Shader
Shader "Custom/WireframeSeeThrough_Geom"
{
Properties
{
_WireColor("Wireframe Color", Color) = (1,1,1,1)
_WireWidth("Wireframe Width", Float) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Conservative True
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
};
struct v2g
{
float4 vertex : SV_POSITION;
};
float4 _WireColor;
float _WireWidth;
appdata vert (appdata v)
{
appdata o;
o.vertex = UnityObjectToClipPos(v.vertex);
return o;
}
[maxvertexcount(18)]
void geom(triangle appdata vertices[3], inout TriangleStream<v2g> triStream)
{
v2g OUT;
float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
faceNormal = normalize(faceNormal);
// Expand each edge into two quads
for (int i = 0; i < 3; ++i)
{
int next = (i + 1) % 3;
float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
float4 normal = float4(faceNormal, 0);
float4 offset = float4(_WireWidth * normalize(cross(normal.xyz, dir.xyz)),0);
float4 p1 = vertices[i].vertex - offset;
float4 p2 = vertices[i].vertex + offset;
float4 p3 = vertices[next].vertex + offset;
float4 p4 = vertices[next].vertex - offset;
// First Quad
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p2; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
// Second Quad
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
OUT.vertex = p4; triStream.Append(OUT);
}
}
float4 frag (v2g f) : SV_Target
{
return _WireColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
Wireframe Shader (with backface cull)
Shader "Custom/WireframeBackfaceCull_Geom"
{
Properties
{
_WireColor("Wireframe Color", Color) = (1,1,1,1)
_WireWidth("Wireframe Width", Float) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Conservative True
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#include "UnityCG.cginc"
struct appdata{
float4 vertex : POSITION;
};
struct v2g{
float4 vertex : SV_POSITION;
float3 world : TEXCOORD0;
};
float4 _WireColor;
float _WireWidth;
v2g vert (appdata v){
v2g o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.world = mul(unity_ObjectToWorld, v.vertex);
return o;
}
float isTriFacingCamera(v2g vertices[3]){
// Calculate the normal of the triangle
float3 v1 = vertices[0].world.xyz;
float3 v2 = vertices[1].world.xyz;
float3 v3 = vertices[2].world.xyz;
float3 normal = cross(v2 - v1, v3 - v1);
// Check if the triangle is facing the camera
float3 viewDirection = normalize(_WorldSpaceCameraPos - v1);
return dot(normal, viewDirection);
}
[maxvertexcount(18)]
void geom(triangle v2g vertices[3], inout TriangleStream<v2g> triStream){
v2g OUT;
if(isTriFacingCamera(vertices) < 0){return;}
float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
faceNormal = normalize(faceNormal);
// Expand each edge into two quads
for (int i = 0; i < 3; ++i){
int next = (i + 1) % 3;
float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
float4 offset = float4(_WireWidth * normalize(cross(faceNormal, dir.xyz)),0);
float4 p1 = vertices[i].vertex - offset;
float4 p2 = vertices[i].vertex + offset;
float4 p3 = vertices[next].vertex + offset;
float4 p4 = vertices[next].vertex - offset;
// First Quad
OUT.world = float3(0,0,0);
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p2; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
// Second Quad
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
OUT.vertex = p4; triStream.Append(OUT);
}
}
float4 frag (v2g f) : SV_Target{
return _WireColor;
}
ENDCG
}
}
FallBack "Diffuse"
}
Opaque Wireframe Shader with Texture
Shader "Custom/WireframeWithTexture"
{
Properties
{
_MainTex("MainTexture", 2D) = "white"{}
_WireColor("Wireframe Color", Color) = (1,1,1,1)
_WireWidth("Wireframe Width", Range(0.001, 0.1)) = 0.05
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Conservative True
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma geometry geom
#include "UnityCG.cginc"
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2g{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 world : TEXCOORD1;
};
sampler2D _MainTex;
float4 _WireColor;
float _WireWidth;
v2g vert (appdata v){
v2g o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.world = mul(unity_ObjectToWorld, v.vertex);
o.uv = v.uv;
return o;
}
float isTriFacingCamera(v2g vertices[3]){
// Calculate the normal of the triangle
float3 v1 = vertices[0].world.xyz;
float3 v2 = vertices[1].world.xyz;
float3 v3 = vertices[2].world.xyz;
float3 normal = cross(v2 - v1, v3 - v1);
// Check if the triangle is facing the camera
float3 viewDirection = normalize(_WorldSpaceCameraPos - v1);
return dot(normal, viewDirection);
}
[maxvertexcount(21)]
void geom(triangle v2g vertices[3], inout TriangleStream<v2g> triStream){
v2g OUT;
//main triangle, that represents the geometry:
OUT.world = float3(1,1,1);
OUT.vertex = vertices[0].vertex;
OUT.uv = vertices[0].uv;
triStream.Append(OUT);
OUT.vertex = vertices[1].vertex;
OUT.uv = vertices[1].uv;
triStream.Append(OUT);
OUT.vertex = vertices[2].vertex;
OUT.uv = vertices[2].uv;
triStream.Append(OUT);
triStream.RestartStrip();
if(isTriFacingCamera(vertices) < 0){return;}
//wireframe as 2 quads, three times:
float3 faceNormal = cross(vertices[1].vertex.xyz - vertices[0].vertex.xyz, vertices[2].vertex.xyz - vertices[0].vertex.xyz);
faceNormal = normalize(faceNormal);
float4 zOffset = float4(0,0,0.00001,0);
// Expand each edge into two quads
for (int i = 0; i < 3; ++i){
int next = (i + 1) % 3;
float4 dir = normalize(vertices[next].vertex - vertices[i].vertex);
float4 offset = float4(_WireWidth * normalize(cross(faceNormal, dir.xyz)),0);
float4 p1 = vertices[i].vertex - offset + zOffset;
float4 p2 = vertices[i].vertex + offset + zOffset;
float4 p3 = vertices[next].vertex + offset + zOffset;
float4 p4 = vertices[next].vertex - offset + zOffset;
// First Quad
OUT.world = float3(0,0,0);
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p2; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
// Second Quad
OUT.vertex = p1; triStream.Append(OUT);
OUT.vertex = p3; triStream.Append(OUT);
OUT.vertex = p4; triStream.Append(OUT);
}
}
float4 frag (v2g f) : SV_Target{
fixed4 col = tex2D(_MainTex, f.uv);
col = lerp(_WireColor, col, f.world.x);
return col;
}
ENDCG
}
}
FallBack "Diffuse"
}
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:
Wireframe by Unity (+ texture + wire color with alpha)
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
// from https://github.com/TwoTailsGames/Unity-Built-in-Shaders/blob/master/DefaultResourcesExtra/VR/Shaders/SpatialMappingWireframe.shader
// This is adjusted version, allowing to tweak color of Wireframe, make it barely visible (alpha of color).
// Allows for a texture.
Shader "Custom/VRWireframe (Unlit Texture)"
{
Properties
{
_WireThickness ("Wire Thickness", RANGE(0, 800)) = 100
_MainTex("MainTexture", 2D) = "white"{}
_WireColor("Wireframe Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
// Wireframe shader based on the the following
// http://developer.download.nvidia.com/SDK/10/direct3d/Source/SolidWireframe/Doc/SolidWireframe.pdf
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "UnityCG.cginc"
float _WireThickness;
sampler2D _MainTex;
fixed4 _WireColor;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2g
{
float4 projectionSpaceVertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldSpacePosition : TEXCOORD1;
};
struct g2f
{
float4 projectionSpaceVertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldSpacePosition : TEXCOORD1;
float4 dist : TEXCOORD2;
};
v2g vert (appdata v)
{
v2g o;
o.projectionSpaceVertex = UnityObjectToClipPos(v.vertex);
o.worldSpacePosition = mul(unity_ObjectToWorld, v.vertex);
o.uv = v.uv;
return o;
}
[maxvertexcount(3)]
void geom(triangle v2g i[3], inout TriangleStream<g2f> triangleStream)
{
float2 p0 = i[0].projectionSpaceVertex.xy / i[0].projectionSpaceVertex.w;
float2 p1 = i[1].projectionSpaceVertex.xy / i[1].projectionSpaceVertex.w;
float2 p2 = i[2].projectionSpaceVertex.xy / i[2].projectionSpaceVertex.w;
float2 edge0 = p2 - p1;
float2 edge1 = p2 - p0;
float2 edge2 = p1 - p0;
// To find the distance to the opposite edge, we take the
// formula for finding the area of a triangle Area = Base/2 * Height,
// and solve for the Height = (Area * 2)/Base.
// We can get the area of a triangle by taking its cross product
// divided by 2. However we can avoid dividing our area/base by 2
// since our cross product will already be double our area.
float area = abs(edge1.x * edge2.y - edge1.y * edge2.x);
float wireThickness = 800 - _WireThickness;
g2f o;
o.worldSpacePosition = i[0].worldSpacePosition;
o.projectionSpaceVertex = i[0].projectionSpaceVertex;
o.dist.xyz = float3( (area / length(edge0)), 0.0, 0.0) * o.projectionSpaceVertex.w * wireThickness;
o.dist.w = 1.0 / o.projectionSpaceVertex.w;
o.uv = i[0].uv;
triangleStream.Append(o);
o.worldSpacePosition = i[1].worldSpacePosition;
o.projectionSpaceVertex = i[1].projectionSpaceVertex;
o.dist.xyz = float3(0.0, (area / length(edge1)), 0.0) * o.projectionSpaceVertex.w * wireThickness;
o.dist.w = 1.0 / o.projectionSpaceVertex.w;
o.uv = i[1].uv;
triangleStream.Append(o);
o.worldSpacePosition = i[2].worldSpacePosition;
o.projectionSpaceVertex = i[2].projectionSpaceVertex;
o.dist.xyz = float3(0.0, 0.0, (area / length(edge2))) * o.projectionSpaceVertex.w * wireThickness;
o.dist.w = 1.0 / o.projectionSpaceVertex.w;
o.uv = i[2].uv;
triangleStream.Append(o);
}
fixed4 frag (g2f i) : SV_Target{
float minDistanceToEdge = min(i.dist[0], min(i.dist[1], i.dist[2])) * i.dist[3];
// Early out if we know we are not on a line segment.
float4 texCol = tex2D(_MainTex, i.uv);
// Smooth our line out
float t = exp2(-2 * minDistanceToEdge * minDistanceToEdge);
t *= _WireColor.a;
fixed4 finalColor = lerp(texCol, _WireColor, t);
finalColor.a = 1;
return finalColor;
}
ENDCG
}
}
}