■ 概要
本記事ではUnityの 「ShaderGraph」 を使ってネオンシェーダーを実装しました。
■ 環境
Unityバージョン:Unity2022.3.44f1
プロジェクトテンプレート:Universal 3D
今回はShaderGraphの機能が標準で搭載されている「URP」レンダリングパイプラインのプロジェクトで実装しています。
※ビルトインのレンダリングパイプラインでも、パッケージを後からインストールしてShaderGraphを使用することができます。
その手順はこちらの記事が参考になります。
また今回はネオンの発光を表現するために、Bloom(ブルーム)のポストエフェクトを適用するためにURPのプロジェクトで実装しました。
URPレンダリングパイプライン以外(HDRP、ビルトイン)でもポストエフェクトを適用することは可能みたいですが、ポストプロセスの機能自体が一番簡素に統合されていて導入しやすいのが「URP」レンダリングパイプラインになります。
※ブルームのポストエフェクトを適用する手順はこちらの記事が参考になります。
■ 実装
※今回の実装はWebカメラで撮影した人物部分のみの実装で、背景は動画素材になります。
※効果が分かりやすいように、ShaderGraph内ではデバッグ用の人物画像を使用しています。
■ 実装のポイント
今回の実装アプローチをざっくり説明すると、カメラの入力画像を2値化して「HDR」カラーとスクリーン合成をしています。
→発光するようなBloomのポストエフェクトを反映させるためには「HDR」カラーである必要があります。
※HDRカラーは通常のカラー(LDR)よりも広い輝度範囲を表現できるため、Bloomの効果でより明るい部分が強調されるようになります。
要所を1つずつ説明します。
① カメラの入力画像をサンプリング
カメラの入力画像をサンプリングします。
Texture2Dのプロパティを定義し、CPUプログラム側からカメラの入力画像をセットできるようにします。
② グレースケール化
カメラの入力画像をグレースケール化します。
グレースケール化については過去に書いた記事で少し説明しているので参考にしていただけるとです。
③ 2値化
グレースケール化した画像を「Step」ノードで2値化します。2値化の閾値は外部から更新できるようにプロパティとして切り出しておきましょう。
(今回のようにカメラの入力画像を2値化する場合、2値化の閾値はライティングなどの撮影環境に応じて適切な値が変わります。スライダーなどUIで調整できるようになっていると良いかもしれません。)
④ 2値化画像を色反転
今回はスクリーン合成によってHDRカラーを適用する際に、
・HDRカラーの色の乗り具合(色味)を調整したい
・人物以外の部分を透過にしたい
という2つの都合があるのため、2値化したカメラの入力画像を色反転させます。
おなじみの「One Minus」ノードです。
今回はHDRカラーが合成される白い部分以外は透過にしたいので、ここでの出力結果をそのままフラグメントシェーダーのアルファに適用します。(黒い部分が透過になる感じです)
⑤ 色味の調整
色反転した2値化画像にそのままHDRカラーをスクリーン合成するとほぼ白色(Bloomの効果で僅かに色がつく程度)になってしまうため、白い部分の色をグレーに調整してベースのHDRカラーの色味が出やすいようにします。
やり方はシンプルに0~1の値を掛け合わせてあげます。この係数もプロパティとして切り出して外部から調整できるようにすると良いかと思います。
⑥ スクリーン合成
⑤の出力結果と、HDRカラーを「Blend」ノードでスクリーン合成します。カラーも外部から更新できるようにプロパティとして定義しておきます。
また、カラーのプロパティ設定からモードを「HDR」に変更しておきます。
この状態のノードのプレビューだと透過したい部分にも色がのってしまっていて分かりづらいので、ShaderGraphエディターの右下に表示されているメインプレビューで確認するとアウトプットが確認しやすいと思います。
⑦ ネオンのカラーをCPUプログラム側から変更
今回はWebカメラの入力画像を表示させたいQuadに、
・↑で実装したShaderを適用したマテリアル
・↓のスクリプト
の2つをアタッチします。
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」に投稿する記事ということで、若干クリスマスに寄せた感じのアプトプットを目標にネオンシェーダーを実装してみました。
今回実装した「2値化」→「スクリーン合成」のアプローチは様々なアウトプットに応用できる基本的な実装なので、覚えておくとアイデア次第で色々面白いものが作れると思います。
ご覧いただきありがとうございました。