GeometryShaderでWireFrame

今回はGeometryShaderでWireFrameを実装していきます。


Shader "Wireframe"
{
	Properties
	{
		_MainTex("Texture", 2D) = "white" {}
		_Line("Line", Range(0., 0.34)) = 0.02
		_Color("color", color) = (1., 1., 1., 1.)
	}
	SubShader
		{
			Tags{ "RenderType" = "Opaque" }
			Pass
			{
				Cull Off
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma geometry geom
				#include "UnityCG.cginc"

				struct v2g {
					float4 pos : SV_POSITION;
				};

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

				v2g vert(appdata_base v) {
					v2g o;
					o.pos = UnityObjectToClipPos(v.vertex);
					return o;
				}

				float _Line;
				fixed4 _Color;

				[maxvertexcount(3)]
				void geom(triangle v2g IN[3], inout TriangleStream<g2f> triStream) {
					g2f o;
					o.pos = IN[0].pos;
					o.bary = float3(1., 0., 0.);
					triStream.Append(o);
					o.pos = IN[1].pos;
					o.bary = float3(0., 1., 0.);
					triStream.Append(o);
					o.pos = IN[2].pos;
					o.bary = float3(0., 0., 1.);
					triStream.Append(o);
				}

				fixed4 frag(g2f i) : SV_Target{
					if (!any(bool3(i.bary.x < _Line, i.bary.y < _Line, i.bary.z < _Line))) {
						discard;
					}
					return _Color;
				}
				ENDCG
			}
		}
}


前回と前々回で説明している部分は割愛します。

GeometryShaderの中身を見てみましょう。
各Primitiveの各頂点に頂点属性として重心座標を出すための値をo.baryに入れてあります。

一つ目の頂点には(1,0,0)
二つ目の頂点には(0,1,0)
三つ目の頂点には(0,0,1)

を入れておきます。


そしてFragmentShaderでピクセルを描画すべきかどうか判定させます。

if (!any(bool3(i.bary.x < _Line, i.bary.y < _Line, i.bary.z < _Line))) {
	discard;
}


こちらのif文ですが

三角形に頂点の1つ目が(1,0,0)
三角形に頂点の2つ目が(0,1,0)
三角形に頂点の3つ目が(0,0,1)

だとして、その間の数値はShaderが線型補間してくれます。
その線型補間してくれた数値がi.baryには格納されているので
その値を使って_Line以下のピクセルをdiscard(ピクセルを破棄)しています。


もうちょい具体的に説明すると
例えば
三角形の中のベクトル(0.5,0.5,0.5)に位置するピクセルを描画すべきかどうか?
という判定をする時に
Propertiesで設定した_Lineの値が0.6だとすると
0.6より0.5のほうが小さい値になるので
そのピクセルを破棄する
という条件分岐になります。


参考
Easy wireframe display with barycentric coordinates - Codeflow