ShaderGraphでネオンシェーダーの実装【Unity、ShaderGraph、ネオン、クリスマス】

# ■ 概要
本記事ではUnityの **「ShaderGraph」** を使ってネオンシェーダーを実装しました。

# ■ 環境

Unityバージョン：Unity2022.3.44f1
プロジェクトテンプレート：Universal 3D


**今回はShaderGraphの機能が標準で搭載されている「URP」レンダリングパイプラインのプロジェクトで実装しています。**

※ビルトインのレンダリングパイプラインでも、パッケージを後からインストールしてShaderGraphを使用することができます。
[その手順はこちらの記事が参考になります。](https://ekulabo.com/unity-built-in-shader-graph)

:::note info
また今回はネオンの発光を表現するために、Bloom（ブルーム）のポストエフェクトを適用するためにURPのプロジェクトで実装しました。

URPレンダリングパイプライン以外（HDRP、ビルトイン）でもポストエフェクトを適用することは可能みたいですが、ポストプロセスの機能自体が一番簡素に統合されていて導入しやすいのが「URP」レンダリングパイプラインになります。

※ブルームのポストエフェクトを適用する手順は[こちらの記事が参考になります。](https://qiita.com/cy_ryosuke_zushi/items/0356bf621f4cf5215f21#bloom)
:::


# ■ 実装

※今回の実装はWebカメラで撮影した人物部分のみの実装で、背景は動画素材になります。

Game画面：
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/3c5bd3b6-d1a6-1233-2bdc-ebd84d2443f2.gif" width="960">


実装したShaderGraphファイル：
![スクリーンショット 2024-12-11 112345.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/56305fa2-c5d6-fcec-4355-80359bf47867.png)

※効果が分かりやすいように、ShaderGraph内ではデバッグ用の人物画像を使用しています。

# ■ 実装のポイント

今回の実装アプローチをざっくり説明すると、**カメラの入力画像を2値化して「HDR」カラーとスクリーン合成**をしています。

:::note info
**→発光するようなBloomのポストエフェクトを反映させるためには「HDR」カラーである必要があります。**

※HDRカラーは通常のカラー(LDR)よりも広い輝度範囲を表現できるため、Bloomの効果でより明るい部分が強調されるようになります。
:::

要所を1つずつ説明します。

## ① カメラの入力画像をサンプリング
カメラの入力画像をサンプリングします。
Texture2Dのプロパティを定義し、CPUプログラム側からカメラの入力画像をセットできるようにします。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/7e0e419f-af50-da50-2942-270fcd55a4fd.png" width="400">


## ② グレースケール化
カメラの入力画像をグレースケール化します。
グレースケール化については[過去に書いた記事](https://qiita.com/generosity_honda/items/f091f8edc7d938c7b2fb#%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB%E3%81%AE%E3%83%9D%E3%82%B9%E3%83%88%E3%82%A8%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B)で少し説明しているので参考にしていただけるとです。

https://qiita.com/generosity_honda/items/f091f8edc7d938c7b2fb#%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%AB%E3%81%AE%E3%83%9D%E3%82%B9%E3%83%88%E3%82%A8%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E9%81%A9%E7%94%A8%E3%81%99%E3%82%8B

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/15f26e02-96fb-b491-6b6e-f15a8c147b74.png" width="400">

## ③ 2値化
グレースケール化した画像を「Step」ノードで2値化します。2値化の閾値は外部から更新できるようにプロパティとして切り出しておきましょう。

（今回のようにカメラの入力画像を2値化する場合、2値化の閾値はライティングなどの撮影環境に応じて適切な値が変わります。スライダーなどUIで調整できるようになっていると良いかもしれません。）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/85bcbbb4-4a41-ad7b-e462-89ed8446af77.png" width="400">


## ④ 2値化画像を色反転
今回はスクリーン合成によってHDRカラーを適用する際に、

・HDRカラーの色の乗り具合（色味）を調整したい
・人物以外の部分を透過にしたい

という2つの都合があるのため、2値化したカメラの入力画像を色反転させます。
おなじみの「One Minus」ノードです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/231114d7-af6f-f970-e078-5ff194d52878.png" width="400">

今回はHDRカラーが合成される白い部分以外は透過にしたいので、ここでの出力結果をそのままフラグメントシェーダーのアルファに適用します。（黒い部分が透過になる感じです）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/5dfa63a7-fc91-7480-885e-571a20a9c18d.png" width="400">

## ⑤ 色味の調整
色反転した2値化画像にそのままHDRカラーをスクリーン合成するとほぼ白色（Bloomの効果で僅かに色がつく程度）になってしまうため、白い部分の色をグレーに調整してベースのHDRカラーの色味が出やすいようにします。

やり方はシンプルに0~1の値を掛け合わせてあげます。この係数もプロパティとして切り出して外部から調整できるようにすると良いかと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/9c0a3d09-c505-8057-22bb-b3683200f8f8.png" width="400">

## ⑥ スクリーン合成
⑤の出力結果と、HDRカラーを「Blend」ノードでスクリーン合成します。カラーも外部から更新できるようにプロパティとして定義しておきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/cbb43e4b-90e1-ee50-0050-7e98541cf745.png" width="400">

また、カラーのプロパティ設定からモードを「HDR」に変更しておきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/b5bd51a9-e39f-665f-2438-7aff8ed2ae53.png" width="400">

この状態のノードのプレビューだと透過したい部分にも色がのってしまっていて分かりづらいので、ShaderGraphエディターの右下に表示されているメインプレビューで確認するとアウトプットが確認しやすいと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730387/dd637284-0944-8b3d-1917-8245448430ae.png" width="400">


## ⑦ ネオンのカラーをCPUプログラム側から変更
今回はWebカメラの入力画像を表示させたいQuadに、

・↑で実装したShaderを適用したマテリアル
・↓のスクリプト

の2つをアタッチします。

```WebCamController.cs
public class WebCamController : MonoBehaviour
{
    // 色相が1周する時間 (秒)
    public float cycleDuration = 1f;
    // HDRカラーの強度 大きいほどより発光した感じが強くなる
    public float intensity = 2f;
    
    int width = 1920;
    int height = 1080;
    int fps = 30;
    
    WebCamTexture webcamTexture;
    Material material;
    
    void Start()
    {
        material = GetComponent<Renderer>().material;
        
        // カメラデバイスの取得
        WebCamDevice[] devices = WebCamTexture.devices;
        // カメラのテクスチャを生成
        webcamTexture = new WebCamTexture(devices[0].name, this.width, this.height, this.fps);
        // 適用
        material.mainTexture = webcamTexture;
        material.SetTexture("_Texture", webcamTexture);
        // スタート
        webcamTexture.Play();
    }
    void Update()
    {
        if (material == null) return;
        
        // 時間に基づいて色相を計算 (0.0～1.0の範囲でループ)
        float hue = (Time.time % cycleDuration) / cycleDuration;
        // 彩度を固定
        float saturation = 1f;
        // 明度を固定
        float value = 1f;
        
        // HSVからRGBに変換
        Color rgbColor = Color.HSVToRGB(hue, saturation, value);
        // HDR強度を適用
        rgbColor *= intensity;
        
        // マテリアルに色を設定
        material.SetColor("_Color", rgbColor);
    }
}
```

# ■ 最後に （感想）

今回は「[Qiita Advent Calendar](https://qiita.com/advent-calendar/2024)」に投稿する記事ということで、若干クリスマスに寄せた感じのアプトプットを目標にネオンシェーダーを実装してみました。
今回実装した「2値化」→「スクリーン合成」のアプローチは様々なアウトプットに応用できる基本的な実装なので、覚えておくとアイデア次第で色々面白いものが作れると思います。

ご覧いただきありがとうございました。