Geometry Shader·线框效果

Geometry Shader是介于 Vertex Shading 和Fragment Shading之间的步骤,其输入是Vertex Shader的输出,输出是Fragment Shader的输入。修改Geometry可以新增或删减顶点创造效果。

Extrude Pyramid

以一个extrude prymaid的shader为例,这个shader将在每一个三角面上新增顶点构成金字塔状凸起:

Shader "Lapu/TrianglePyramidExtrusion"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
        _ExtrusionFactor("Extrusion factor", float) = 0
    }
        SubShader
        {
            Tags { "RenderType" = "Opaque" }
            Cull Off
            LOD 100

            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma geometry geom
                #pragma fragment frag
                // make fog work
                #pragma multi_compile_fog

                #include "UnityCG.cginc"
                struct appdata
                {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                    float3 normal : NORMAL;
                };

                struct v2g
                {
                    float4 vertex : SV_POSITION;
                    float2 uv : TEXCOORD0;
                    float3 normal : NORMAL;
                };

                struct g2f
                {
                    float2 uv : TEXCOORD0;
                    UNITY_FOG_COORDS(1)
                    float4 vertex : SV_POSITION;
                    float4 color : COLOR;
                };

                sampler2D _MainTex;
                float4 _MainTex_ST;
                float _ExtrusionFactor;

                v2g vert(appdata v)
                {
                    v2g o;
                    o.vertex = v.vertex;
                    o.uv = v.uv;
                    o.normal = v.normal;
                    return o;
                }

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

                    float4 barycenter = (IN[0].vertex + IN[1].vertex + IN[2].vertex) / 3;
                    float3 normal = (IN[0].normal + IN[1].normal + IN[2].normal) / 3;

                    for (int i = 0; i < 3; i++) {
                        int next = (i + 1) % 3;

                        o.vertex = UnityObjectToClipPos(IN[i].vertex);
                        UNITY_TRANSFER_FOG(o,o.vertex);
                        o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                        o.color = fixed4(0.0, 0.0, 0.0, 1.0);
                        triStream.Append(o);

                        o.vertex = UnityObjectToClipPos(barycenter + float4(normal, 0.0) * _ExtrusionFactor);
                        UNITY_TRANSFER_FOG(o,o.vertex);
                        o.uv = TRANSFORM_TEX(IN[i].uv, _MainTex);
                        o.color = fixed4(1.0, 1.0, 1.0, 1.0);
                        triStream.Append(o);

                        o.vertex = UnityObjectToClipPos(IN[next].vertex);
                        UNITY_TRANSFER_FOG(o,o.vertex);
                        o.uv = TRANSFORM_TEX(IN[next].uv, _MainTex);
                        o.color = fixed4(0.0, 0.0, 0.0, 1.0);
                        triStream.Append(o);

                        triStream.RestartStrip();
                    }

                    triStream.RestartStrip();
                }

                fixed4 frag(g2f i) : SV_Target
                {
                    // sample the texture
                    fixed4 col = tex2D(_MainTex, i.uv) * i.color;
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

一些常用方法:

Wireframe

原理文档→文档,实现效果如下:

和上述shader同理,获取三角面的三个顶点。在Geom shader中根据三边的坐标计算三角形面积,然后根据面积计算三边的高度。之后在Frag shader中计算vertex到三边的最短距离,具体方法为取三角形三边中最短的高,若顶点距离边缘的距离小于阈值,则绘制线框。

smooth参数和最后一个lerp运算用于平滑base color和线框color。

shader代码如下:

Shader "Lapu/Wireframe"
{
	Properties
	{
		_MainTex("MainTex", 2D) = "white" {}
		_WireThickness("Wire Thickness", RANGE(0, 800)) = 100
		_WireSmoothness("Wire Smoothness", RANGE(0, 20)) = 3
		[HDR]_WireColor("Wire Color", Color) = (0.0, 1.0, 0.0, 1.0)
		_Opacity("Opacity", Range(0 , 1)) = 4.5
		_BaseColor("Base Color", Color) = (0.0, 0.0, 0.0, 1.0)
	}

		SubShader
		{
			Tags{"RenderType" = "Transparent" "Queue" = "Transparent"}

			Pass
			{
				Cull Off 
				//AlphaToMask On 

				Blend One OneMinusSrcAlpha
				
				CGPROGRAM
				#pragma vertex vert
				#pragma geometry geom
				#pragma fragment frag

				#include "UnityCG.cginc"

				sampler2D _MainTex; 
				float4 _MainTex_ST;
				float _WireThickness;
				float _WireSmoothness;
				float4 _WireColor;
				float4 _BaseColor;
				float _Opacity;

				struct appdata
				{
					float4 vertex : POSITION;
					float2 texcoord0 : TEXCOORD0;
					UNITY_VERTEX_INPUT_INSTANCE_ID
				};

				struct v2g
				{
					float4 vertex : SV_POSITION;
					float2 uv0 : TEXCOORD0;
					float4 worldSpacePosition : TEXCOORD1;
					UNITY_VERTEX_OUTPUT_STEREO	//声明该顶点是否位于视线域中,来判断这个顶点是否输出到片段着色器。
				};

				struct g2f
				{
					float4 vertex : SV_POSITION;
					float2 uv0 : TEXCOORD0;
					float4 worldSpacePosition : TEXCOORD1;
					float4 dist : TEXCOORD2;
					float4 color : COLOR;//Todo:only show rect frame
					UNITY_VERTEX_OUTPUT_STEREO
				};

				v2g vert(appdata v)
				{
					v2g o;
					o.vertex = UnityObjectToClipPos(v.vertex);
					o.worldSpacePosition = mul(unity_ObjectToWorld, v.vertex);
					o.uv0 = TRANSFORM_TEX(v.texcoord0, _MainTex);
					return o;
				}

				[maxvertexcount(3)]
				void geom(triangle v2g i[3], inout TriangleStream<g2f> triangleStream)
				{
					float2 p0 = i[0].vertex.xy;
					float2 p1 = i[1].vertex.xy ;
					float2 p2 = i[2].vertex.xy;

					float2 edge0 = p2 - p1;
					float2 edge1 = p2 - p0;
					float2 edge2 = p1 - p0;

					//get area of triangle frag
					float area = abs(edge1.x * edge2.y - edge1.y * edge2.x);
					float wireThickness = 800 - _WireThickness;

					g2f o;

					o.uv0 = i[0].uv0;
					o.worldSpacePosition = i[0].worldSpacePosition;
					o.vertex = i[0].vertex;
					o.dist.xyz = float3((area / length(edge0)), 0.0, 0.0) * o.vertex.w * wireThickness;
					o.dist.w = 1.0 / o.vertex.w;
					o.color = float4(_WireColor);
					triangleStream.Append(o);

					o.uv0 = i[1].uv0;
					o.worldSpacePosition = i[1].worldSpacePosition;
					o.vertex = i[1].vertex;
					o.dist.xyz = float3(0.0, (area / length(edge1)), 0.0) * o.vertex.w * wireThickness;
					o.dist.w = 1.0 / o.vertex.w;
					o.color = float4(_WireColor);
					triangleStream.Append(o);

					o.uv0 = i[2].uv0;
					o.worldSpacePosition = i[2].worldSpacePosition;
					o.vertex = i[2].vertex;
					o.dist.xyz = float3(0.0, 0.0, (area / length(edge2))) * o.vertex.w * wireThickness;
					o.dist.w = 1.0 / o.vertex.w;
					o.color = float4(_WireColor);
					triangleStream.Append(o);
				}

				fixed4 frag(g2f i) : SV_Target
				{
					float minDistanceToEdge = min(i.dist[0], min(i.dist[1], i.dist[2])) * 0.01;

					float4 baseColor = _BaseColor * tex2D(_MainTex, i.uv0);

					// Early out if we know we are not on a line segment.
					if (minDistanceToEdge > 0.99)
					{
						return fixed4(baseColor.rgb,1) * _Opacity;
					}

					// Smooth our line out
					float t = exp2(_WireSmoothness * -1.0 * minDistanceToEdge * minDistanceToEdge);
					fixed4 finalColor = lerp(baseColor, i.color, t);
					finalColor.a = t;

					return finalColor;
				}
				ENDCG
			}
		}
}