初心者がToonShaderを作ってみた

# 初めに
キャラクターをアニメ調で表現するToonshaderを書いてみたので勉強用に記録します。
解説はコードのコメントに書きました。
ユニバーサルレンダーパイプラインを使用していますので、インストールされてない方Window > My Asset > Unity Registry > Universal RPより、インストールしてください。

# Shaderの使い方でお世話になった記事🙇‍♂️

https://qiita.com/Yumuru/items/77a642004d015b83e0ce

# 実装コード
```
Shader "Custom/Toon"
{
   Properties
    {
        _Color ("Color", Color) = (0.3, 0.3, 0.3, 1)//GameObjectの色
        _Width ("width",float) = 0.1//枠の大きさ
        _FrameColor("FrameColor",Color) = (0,0,0,1)//枠の色
    }
   SubShader
    {
        Tags {
            "RenderPipeline"="UniversalPipeline"//UniversalPipelineを使用
        }
 
        LOD 100//閾値

        Pass//枠
        {
            Tags{ "LightMode" = "UniversalForward"}//URPにおける描画順番の指定に必要
            Cull Front//描画順番　後
            HLSLPROGRAM//HLSLを使用
            #pragma vertex vert//頂点の操作
            #pragma fragment frag//画素の色塗
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//Core.hlslをimport
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//Lighting.hlslをimport
            
            struct appdata
            {
                float4 vertex : POSITION;// セマンティクス(頂点の位置)
                float3 normal : NORMAL;//セマンティクス(頂点の法線)
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;//他のレンダリング対象に対する色
                float3 normalWS : TEXCOORD1;//セマンティクス(2番目のUV座標)
            };
            float _Width;
            v2f vert (appdata v)
            {
                v2f o;
                half4x4 scaleMatrix = half4x4(1 +_Width, 0, 0, 0,
                                             0, 1 + _Width, 0, 0,
                                             0, 0, 1 + _Width, 0,
                                             0, 0, 0, 1);
                v.vertex = mul(scaleMatrix, v.vertex);//行列計算してGameObjectの拡大
                o.vertex = TransformObjectToHClip(v.vertex);
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                return o;
            }
            float4 _FrameColor;
            float4 frag (v2f i) : SV_Target
            {                
                return _FrameColor;//枠の色
            }
            ENDHLSL
        }

        Pass
        {
            Cull Back//描画順番　先

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//Core.hlslをimport
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//Lighting.hlslをimport
            
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normalWS : TEXCOORD1;//セマンティクス(2番目のUV座標)
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                return o;
            }
            float4 _Color;
            float4 frag (v2f i) : SV_Target
            {                
                Light lt = GetMainLight();//光を取得
                float4 col = _Color;
                float strength = dot(lt.direction, i.normalWS);//光の強さを計算
                if(strength <= 0.2)//強さによって場合分け
                {
                    strength = 0.1;
                }
                else if(strength <= 0.66)
                {
                    strength = 0.5;
                }
                else
                {
                    strength = 1;
                }
                float4 lightColor = float4(lt.color, 1);//光の色
                return col* lightColor*strength;//最終的な色の計算
            }
            ENDHLSL
        }
}
}
```
# 完成
![スクリーンショット 2025-02-11 215010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4006397/d16940ad-aaac-d258-c6c7-18ff427caba2.png)

# 補足
HLSLPROGRAMとCGPROGRAMは大体似たようなもの(初心者の意見)
それと、if 文を使うと処理が重くなるという記事を見たので、ifを使わないやり方も載せておきます

```
Shader "Custom/Toon"
{
   Properties
    {
        _Color ("Color", Color) = (0.3, 0.3, 0.3, 1)//GameObjectの色
        _Width ("width",float) = 0.1//枠の大きさ
        _FrameColor("FrameColor",Color) = (0,0,0,1)//枠の色
        _RampTex ("Ramp", 2D) = "white"{}
    }
   SubShader
    {
        Tags {
            "RenderPipeline"="UniversalPipeline"//UniversalPipelineを使用
        }
 
        LOD 100//閾値

        Pass//枠
        {
            Tags{ "LightMode" = "UniversalForward"}//URPにおける描画順番の指定に必要
            Cull Front//描画順番　後
            HLSLPROGRAM//HLSLを使用
            #pragma vertex vert//頂点の操作
            #pragma fragment frag//画素の色塗
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//Core.hlslをimport
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//Lighting.hlslをimport
            
            struct appdata
            {
                float4 vertex : POSITION;// セマンティクス(頂点の位置)
                float3 normal : NORMAL;//セマンティクス(頂点の法線)
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;//他のレンダリング対象に対する色
                float3 normalWS : TEXCOORD1;//セマンティクス(2番目のUV座標)
            };
            float _Width;
            v2f vert (appdata v)
            {
                v2f o;
                half4x4 scaleMatrix = half4x4(1 +_Width, 0, 0, 0,
                                             0, 1 + _Width, 0, 0,
                                             0, 0, 1 + _Width, 0,
                                             0, 0, 0, 1);
                v.vertex = mul(scaleMatrix, v.vertex);//行列計算してGameObjectの拡大
                o.vertex = TransformObjectToHClip(v.vertex);//変換
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                return o;
            }
            float4 _FrameColor;
            float4 frag (v2f i) : SV_Target
            {                
                return _FrameColor;//枠の色
            }
            ENDHLSL
        }

        Pass
        {
            Cull Back//描画順番　先

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"//Core.hlslをimport
            #include  "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"//Lighting.hlslをimport
            
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normalWS : TEXCOORD1;//セマンティクス(2番目のUV座標)
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);//変換
                VertexNormalInputs normal = GetVertexNormalInputs(v.normal);
                o.normalWS = normal.normalWS;
                return o;
            }
            float4 _Color;
            sampler2D _RampTex;
            float4 frag (v2f i) : SV_Target
            {                
                Light lt = GetMainLight();//光を取得
                float4 col = _Color;
                float strength = (dot(lt.direction, i.normalWS)+1)*0.5;//光の強さを計算
                float3 ramp = tex2D(_RampTex, float2(strength, 0.5)).rgb;//テキスチャの色の取得
                float r = dot(ramp, float3(0.333, 0.333, 0.333));//内積による色の強度の取得
                float4 lightColor = float4(lt.color, 1);//光の色
                return col* lightColor*r;//最終的な色の計算
            }
            ENDHLSL
        }
}
}
```
# 使用方法
以下の写真をtoon.shaderのマテリアルのRampにアタッチします
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4006397/5733281a-534e-4bf9-6619-c0d37b8cbb80.jpeg" width=50%>

![Image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4006397/3b67231b-2d3e-ac43-58b6-ba95f4c9fa5d.png)

# 参考にしたサイトの紹介
枠の作成

https://zenn.dev/kento_o/articles/f5ff889e6f4f50

陰の作成

https://redhologerbera.hatenablog.com/entry/2022/11/11/231804

shaderのセマンティクスの解説

https://qiita.com/nmxi/items/bfd10a3b3f519878e74e

ifを使わずに実行する方法の参考

https://nn-hokuson.hatenablog.com/entry/2017/03/27/204255

# 最後に
今回が初めて書いた記事なので、うまくかけてないかもしれません。だから、疑問とかあれば、コメントとか書いていただければ、拙い知識ではありますが、できるだけ答えようと思います。