【Unity】ワイヤーフレームシェーダー

ポリゴンのワイヤーフレームを描画するシェーダーを作りました。
![UnityちゃんInワイヤーフレーム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/551eafe6-bcff-0e58-3dea-6b79c4c0ed36.png)


# 実現方法
## ポリゴンの辺
ジオメトリシェーダーを使います。
ジオメトリシェーダーではポリゴンの三角形単位で処理を行えるので、XYZそれぞれを各頂点に対応させて1.0を代入する項目を作ります。
```hlsl:重心座標系の値の元ネタを仕込む処理
/**
 * [ジオメトリシェーダー用]
 * パラメータをジオメトリックシェーダーの返却値となるTriangleStreamに追加する
 */
void SetTriVerticesToStream(g2f param[3], inout TriangleStream<g2f> outStream) {
    [unroll]
    for (int i = 0; i < 3; i++) {
        // ワイヤーフレーム描画用
        param[i].baryCentricCoords = float3(i == 0, i == 1, i == 2);

        outStream.Append(param[i]);
    }

    outStream.RestartStrip();
}
```

これがラスタライズされると、重心座標系となり、それぞれ頂点から向かい合う辺に向かって1.0→0.0へと変化するようになります。
ラスタライズの仕組みを巧く使った面白いテクニックですね。

XYZそれぞれの値を可視化しました。

#### Xの値
![重心系座標のX.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/d0d54c3f-8807-4c40-202a-c52156a499b9.png)

#### Yの値
![重心座標系のY.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/9575372b-3922-ed92-06db-cab1ff1d3b70.png)

#### Zの値
![重心座標系のZ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/70fe4315-b320-7fc7-27e2-b9fc8c2c1b8f.png)

上記のようになるので、XYZどれかの値が0付近の部分にのみ色を付けるとワイヤーフレームとなります。

## ワイヤーフレームの幅
シェーダープロパティでピクセル単位で幅を指定できるようにしました。

フラグメントシェーダーにて重心座標系の値の偏微分を取り、1ピクセルでの変化量を求めます。
これに指定された幅の値を掛けると、太さ分に該当する重心座標系の値の変化量（の概算）を算出できます。
重心座標系の値がこの値以下の場合にのみワイヤーフレームを描画するようにします。

隣接するポリゴンに対してもワイヤーフレームが描画されるため線の幅は2倍になるので、指定された幅の1/2になるように描画します。

```hlsl:
// fwidthで1ピクセルの変化量を求め、ワイヤーを描く値を算出
float3 thOfJudgingSides = fwidth(input.baryCentricCoords) * _WireframeWidth * 0.5;

// 処理中のピクセルの座標が_WireframeWidthピクセル分で増加する値より小さい場合は描画対象
// （重心座標系では頂点から向かいの辺に向かって座標が1→0と変化することを利用）
float3 isOnSides = 1.0 - pow(saturate(input.baryCentricCoords / thOfJudgingSides), 4.0);
float isOnSide = max(max(isOnSides.x, isOnSides.y), isOnSides.z);

surfaceData.albedo = lerp(surfaceData.albedo, _WireframeColor.rgb, isOnSide);
```

境目が少しなだらかになるように4乗していますが、この辺りはお好みで。


# 実行結果
#### Emissionでワイヤーフレーム
![Emissionでワイヤーフレーム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/f9d8a8da-da5d-d7be-c3b5-56645f3dddb6.png)

#### BaseMap + ワイヤーフレーム
![BaseMap+ワイヤーフレーム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/2c3b0cb8-5bc5-7b30-1599-a62d880dba4c.png)

#### ワイヤーフレーム部分を透明にして中抜き
![透明ワイヤーフレーム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1605307/78bb9cee-8162-36f3-7bb5-7d42d43793bf.png)


# コード全文
コード全文です。
ライティングはLitシェーダーの関数を利用しています。

<details><summary>ワイヤーフレームシェーダーコード全部</summary>

```hlsl:ワイヤーフレームシェーダー
Shader "Custom/Wireframe" {
    Properties {
        [Header(Albedo)]
        [MainColor] _BaseColor("Base Color", Color) = (1.0, 1.0, 1.0, 1.0)
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}

        [Header(NormalMap)]
        [Toggle(_NORMALMAP)] _NORMALMAP("Normal Map使用有無", Int) = 0
        [NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {}
        [HideInInspector] _BumpScale("Bump Scale", Float) = 1.0

        [Header(Occlution)]
        [Toggle(_OCCLUSIONMAP)] _OCCLUSIONMAP("Occlusion Map使用有無", Int) = 0
        [NoScaleOffset] _OcclusionMap("Occlusion Map", 2D) = "white" {}
        [HideInInspector] _OcclusionStrength("Strength", Range(0.0, 1.0)) = 1.0

        [Header(Metallic and Smoothness)]
        _Smoothness("Smoothness(Map使用時はAlpha=1の箇所の値)", Range(0.0, 1.0)) = 0.0
        [Toggle(_METALLICSPECGLOSSMAP)] _METALLICSPECGLOSSMAP("Metallic and Smoothness Map使用有無", Int) = 0
        _Metallic("Metallic(Map不使用時のみ)", Range(0.0, 1.0)) = 0.0
        [NoScaleOffset] _MetallicGlossMap("Metallic and Smoothnes Map", 2D) = "white" {}

        [Header(Emission)]
        [Toggle(_EMISSION)] _EMISSION("Emission使用有無", Int) = 0
        [HDR] _EmissionColor("Emission Color", Color) = (0.0 ,0.0, 0.0)
        [NoScaleOffset] _EmissionMap("Emission Map", 2D) = "white" {}

        [Header(Wireframe)]
        _WireframeWidth("ワイヤーフレーム幅", Range(1, 50)) = 1
        _WireframeColor("ワイヤーフレーム色", Color) = (0.0, 0.0, 1.0, 1.0)
        _WireframeEmissionColor("ワイヤーフレームのEmission Color", Color) = (0.0, 0.0, 0.0)
        
        [Space(10)]
        [KeywordEnum(Off, Front, Back)] _Cull ("Cull", Int) = 2
    }

    SubShader {
        Tags {
            "Queue" = "Transparent" 
            "RenderType" = "Transparent"
            "RenderPipeline" = "UniversalPipeline"
            "UniversalMaterialType" = "Lit"
        }
        Blend SrcAlpha OneMinusSrcAlpha
        LOD 300
        Cull [_Cull]

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        ENDHLSL

        Pass {
            Name "Wireframe"
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM

            // -------------------------------------
            // Material Keywords
            #pragma shader_feature_local _NORMALMAP
            #pragma shader_feature_local_fragment _ALPHATEST_ON
            #pragma shader_feature_local_fragment _ALPHAPREMULTIPLY_ON
            #pragma shader_feature_local_fragment _EMISSION
            #pragma shader_feature_local_fragment _METALLICSPECGLOSSMAP
            #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature_local_fragment _OCCLUSIONMAP
            #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature_local_fragment _ENVIRONMENTREFLECTIONS_OFF
            #pragma shader_feature_local_fragment _SPECULAR_SETUP
            #pragma shader_feature_local _RECEIVE_SHADOWS_OFF

            // -------------------------------------
            // Universal Pipeline keywords
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
            #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
            #pragma multi_compile_fragment _ _SHADOWS_SOFT

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing


            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl"

            #pragma vertex Vert
            #pragma geometry Geom
            #pragma fragment Frag

            #pragma require geometry


            // ---------------------------------------------------------------------------------------
            // 変数宣言
            // ---------------------------------------------------------------------------------------
            float _WireframeWidth;
            half4 _WireframeColor;
            half3 _WireframeEmissionColor;


            // ---------------------------------------------------------------------------------------
            // 構造体
            // ---------------------------------------------------------------------------------------
            struct v2g {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
                float3 positionWS : TEXCOORD2;
                half3 vertexSH : TEXCOORD3;
                
#ifdef _NORMALMAP
                half4 tangentWS : TEXCOORD4;
#endif
            };

            struct g2f {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normalWS : TEXCOORD1;
                float3 positionWS : TEXCOORD2;
                half3 vertexSH : TEXCOORD3;
                float3 baryCentricCoords : TEXCOORD4;
#ifdef _NORMALMAP
                half4 tangentWS : TEXCOORD5;
#endif
            };


            // ---------------------------------------------------------------------------------------
            // メソッド
            // ---------------------------------------------------------------------------------------
            /**
             * [ジオメトリシェーダー用]
             * パラメータをジオメトリックシェーダーの返却値となるTriangleStreamに追加する
             */
            void SetTriVerticesToStream(g2f param[3], inout TriangleStream<g2f> outStream) {
                [unroll]
                for (int i = 0; i < 3; i++) {
                    // ワイヤーフレーム描画用
                    param[i].baryCentricCoords = float3(i == 0, i == 1, i == 2);

                    outStream.Append(param[i]);
                }

                outStream.RestartStrip();
            }


            // ---------------------------------------------------------------------------------------
            // シェーダー関数
            // ---------------------------------------------------------------------------------------
            /**
             * 頂点シェーダー
             */
            v2g Vert(Attributes input) {
                v2g output;

                Varyings varyings = LitPassVertex(input);
                output.uv = varyings.uv;
                output.normalWS = varyings.normalWS;
                output.positionWS = varyings.positionWS;
                output.positionCS = varyings.positionCS;
                output.vertexSH = varyings.vertexSH;

#ifdef _NORMALMAP
                output.tangentWS = varyings.tangentWS;
#endif

                return output;
            }

            /**
             * ジオメトリシェーダー
             */
            [maxvertexcount(3)]
            void Geom(triangle v2g inputs[3], inout TriangleStream<g2f> outStream) {
                g2f outputs[3];

                [unroll]
                for (int i = 0; i < 3; i++) {
                    outputs[i].positionWS = inputs[i].positionWS;
                    outputs[i].normalWS = inputs[i].normalWS;
                    outputs[i].positionCS = inputs[i].positionCS;
                    outputs[i].uv = inputs[i].uv;
                    outputs[i].vertexSH = inputs[i].vertexSH;
#ifdef _NORMALMAP
                    outputs[i].tangentWS = inputs[i].tangentWS;
#endif
                }

                SetTriVerticesToStream(outputs, outStream);
            }

            /**
             * フラグメントシェーダー
             */
            half4 Frag(g2f input) : SV_Target {
                Varyings varyings = (Varyings)0;
                varyings.positionCS = input.positionCS;
                varyings.uv = input.uv;
                varyings.positionWS = input.positionWS;
                varyings.normalWS = input.normalWS;
                varyings.vertexSH = input.vertexSH;
#ifdef _NORMALMAP
                varyings.tangentWS = input.tangentWS;
#endif

                SurfaceData surfaceData;
                InitializeStandardLitSurfaceData(input.uv, surfaceData);

                InputData inputData;
                InitializeInputData(varyings, surfaceData.normalTS, inputData);
                inputData.vertexLighting = VertexLighting(inputData.positionWS, inputData.normalWS);


                /* ワイヤーフレーム */
                // fwidthで1ピクセルの変化量を求め、ワイヤーを描く値を算出
                float3 thOfJudgingSides = fwidth(input.baryCentricCoords) * _WireframeWidth * 0.5;

                // 処理中のピクセルの座標が_WireframeWidthピクセル分で増加する値より小さい場合は描画対象
                // （重心座標系では頂点から向かいの辺に向かって座標が1→0と変化することを利用）
                float3 isOnSides = 1.0 - pow(saturate(input.baryCentricCoords / thOfJudgingSides), 4.0);
                float isOnSide = max(max(isOnSides.x, isOnSides.y), isOnSides.z);

                surfaceData.albedo = lerp(surfaceData.albedo, _WireframeColor.rgb, isOnSide);
                surfaceData.alpha = lerp(surfaceData.alpha, _WireframeColor.a, isOnSide);
                surfaceData.emission = lerp(surfaceData.emission, _WireframeEmissionColor.rgb, isOnSide);

                half4 color = UniversalFragmentPBR(inputData, surfaceData);

                clip(color.a <= 0 ? -1 : 1);

                return color;
            }
            ENDHLSL
        }
    }

    FallBack "Universal Render Pipeline/Lit"
}
```

</details>