はじめに
こんにちは、Putinuです。
本記事では、UnityのShaderGraphの機能を用いて動く2Dのハーフトーンシェーダーを制作していきます。
技術記事を書くことにはまだ不慣れのため、誤字脱字・表現の誤り等が含まれている可能性があります。
発見した際はコメントや私のTwitter(@putinu3)の方にでもご連絡頂けると幸いです。
本記事作成の経緯
時折お洒落なUI・影の表現として登場するハーフトーンですが、「作りたい!」と興味を持ち調べてみたところ、あまりUI周りに関連した記事を見つけることが出来ませんでした。(3Dの影に関するハーフトーンの記事は沢山見られましたが...。)
であれば、自身でUI向けのハーフトーンシェーダーを制作し、一例として記事でご紹介できればと考えました。
興味がありましたら、ご一読ください。
注意点
- 本記事では、UIのハーフトーンシェーダーの作り方にのみにフォーカスしています。
- 3D向けのハーフトーンシェーダーについては触れません。
- UIを表示する
Canvasについて、Screen Space - Overlayでは動作しません。-
Screen Space - Cameraのみで正しく動作します。 - 詳細は後述します。
-
今回作るもの
本記事では以下のようなものを制作していきます。
| プロパティ名 | 型 | 説明 |
|---|---|---|
| MainTex | Texture 2D |
Spriteのテクスチャ情報を保持する |
| Roughness | Float | マルの粗さ |
| MaxCircleSize | Float | マルのサイズ上限値 |
| HideOffset | Float | ハーフトーンの消える位置 |
| HideDistance | Float | ハーフトーンが消えるまでの距離 |
| Rotation | Float |
UVの回転量 |
| MoveSpeed | Float | ハーフトーンの移動速度 |
| MoveDirection | Vector 2 | ハーフトーンの移動方向 |
環境
- Unity 2021.3.9f1 Personal
- Universal RP 12.1.7
1. ハーフトーン(Halftone)とは
網点(あみてん)またはハーフトーン(英: Halftone)とは、グレイスケールやカラーの画像を限られた色数(例えば、白い紙上の黒い点など)の小さな点のパターンで表すことで印刷可能にしたものである。
マル(水玉模様)の大きさを調整することによって、影などの色の濃淡を表現していたようです。
主に印刷のために用いられていたようですが、今ではデザインの一種としても使用されています。
ゲームでも、UIの一部に使用されている例がいくつか見られました。
以下リンクより、ハーフトーンを利用した様々なゲームUIを確認することができます。
2. 準備する
シェーダーを作るための環境を準備します。
UnityやShaderGraphの基本的な操作については触れません。
適宜チュートリアルページをご確認ください。
2.1. ファイルの作成
まず、2D (URP)で新しいUnityプロジェクトを作成します。
プロジェクトが起動したら、Projectウィンドウ内で右クリックから、Create > Shader Graph > URP > Sprite Unlit Shader Graphを選択し、新しいファイルを作成します。
2.2. 表示するUIの配置
Hierarchyウィンドウ内で右クリックし、UI > Imageを選択し、今回のシェーダーを適用するゲームオブジェクトを作成します。
使用するSpriteについては、独自で用意するか、以下トグル内の素材を使用してください。
作成すると、その親オブジェクトとしてCanvasが作成されるのでそれを選択します。そして、Inspectorウィンドウ内のCanvas > Render ModeをScreen Space - Cameraへと変更しましょう。
Render CameraにMain Cameraをアタッチすることも忘れずに。

Screen Space - Overlayにおける問題
2022/9/18現在、Unity2021.2以降において、CanvasのRender ModeをScreen Space - Overlayに設定している場合、UIにシェーダーを適用しようとすると透過すべき部分が透過されず、真っ黒になってしまうというバグ(?)が存在しています。
2D Objectとして利用する場合であれば問題ありませんが、UIとして使用する場合には以上の問題が発生するため注意が必要です。
Unityのフォーラムにおいても、この問題に関するスレッドが立ち上げられていますが、現在のところ改善される様子はなく、素直にScreen Space - Cameraを使用することが解決策として挙げられています。
2.3. Materialの作成・アタッチ
Projectウィンドウより、先ほど作成したシェーダーファイルを選択した状態で右クリックし、Create > Materialからシェーダーを適用した状態のMaterialを作成します。
そしてシェーダーを適用するImageをHierarchyウィンドウから選択し、先ほど作成したマテリアルをInspectorウィンドウ内のMaterial欄にアタッチさせましょう。

以上で準備は完了です。
3. 要素ごとに作っていく
はじめに、今回のハーフトーンシェーダーを作る上で必要な構成要素を考えます。
その上で実際にUnity上でシェーダー作成を行っていきます。
今回は以下の手順で進めることとします。
- ハーフトーンの形を作る
- 縦横比を維持できるようにする(綺麗なマルにする)
- 位置に応じてマルを小さくする
- 回転させる
- 移動させる
- テクスチャに合わせる
3.1. ハーフトーンの形を作る
ハーフトーンの模様を作るにあたって、以下のことを意識する必要があります。
- 大きさが変化する
- 等間隔にある(パターンになっている)
- 移動する
まず「大きさが変化する」ことに関しては、中心が1、外側が0となっているものを、Step関数を用いて二値化することで表現が出来るでしょう。
続いて「等間隔にある」ことは、揺らぎの無いVoronoiノードを使用してAngle Offsetを0にする。
「移動する」ことは、その入力に使用するUVをスクロールさせることで表現が出来そうです。

以下画像のように配置し、出力先をAlphaに繋げます。

Voronoiノードは各マルの中心が黒色の0になっています。そのため、Step関数を用いて二値化した後、One Minusノードを使用して値を反転させています。
また、VoronoiノードのCall Densityについては、粗さを自由にカスタマイズできるようにFloat型のプロパティRoughnessを用意し、接続しておきます。
ここまで作った状態でSave Assetを実行すると、以下のような状態になっています。
3.2. 縦横比を維持できるようにする
上画像のように縦長や横長のSpriteを扱う場合は、それに合わせてUVが0~1の範囲内で引き延ばされてしまいます。
その結果、横に引き延ばされたマルが描画されてしまいます。以上のような問題を解決するために、縦横比を維持させる必要があります。
具体的には、現在0~1の範囲しか持っていないUVの範囲をSpriteの縦横比に合わせて変化させる必要があります。
ここではSpriteの縦の長さを1として固定します。
そして、横の長さが縦の長さに対してどれほど長いかを調べ、その比率で元のUVと掛け合わせます。
これでUVの範囲を変化させることができます。
Spriteの縦横の長さをそれぞれw、hとし、初めのUVの縦横の長さをu0、v0と置くと、算出されるu、vは、
(u, v) = (\frac{w\cdot {u_0}} h, v_0)
となります。
具体例(縦:100、横:200のとき)
例えば、Spriteの縦の長さが100、横の長さが200とします。
この場合であれば、上記の計算式に落とし込むと、
(u, v) = (2u_0, v_0)
となります。つまり、u軸方向(横方向)に2倍することで綺麗なマルを描画できるようになります。
ShaderGraphでは、TexelSizeノードを用いることで、テクスチャの縦横の長さを取得することができます。
今回はこれを用いて、縦横比を維持できるようにします。

Textureに接続しているMainTexプロパティは、画像左下の+マークからTexture 2Dを選択し、名前をMainTexとして作成します。
プロパティのReference名は_MainTexとなっていることを確認してください。
先ほどの等式をShaderGraphに落とし込むと、以下の画像のようになります。

縦横比を維持した状態の値が一番右側のVector 2ノードより出力されています。
この出力値をVoronoiノードのUVと接続することで、綺麗なマルへと戻すことができます。

3.3. 位置に応じてマルを小さくする
今回、ハーフトーンはVoronoiノードの出力値をStep関数で二値化することで表現するようにしました。
また、この境界値を変化させることでマルの大きさを変化させることが出来るようにしました。
よって、位置に応じてマルを小さくするには、UV座標に応じて二値化する境界値を変化できるようにすれば良さそうです。
3.3.1. UVと直接接続する

UVノードの出力をStepノードのEdgeに接続しました。
すると、以下画像のように左端が大きさ0、右端が大きさ1のハーフトーンが作成されました。

3.3.2. 上限を設ける
続いて、マルのサイズに上限を設けられるようにします。
上限・下限を設定するためには、Clampノードを利用することが出来ます。
UVノードとStepノードの間に配置し、Maxに新しくFloat型のプロパティMaxCircleSizeを設定します。
Minについてはお好みでどうぞ。

これでStepノードへ与える境界値に制限を与えることが出来ました。
3.3.3. 消える位置を調整できるようにする
次に、ハーフトーンが消えていく位置を調整できるようにします。
元々持っているUV座標の位置をずらすことにより、ハーフトーンの消える位置を調節します。
以下のように、境界値が0となる位置をずらすイメージです。

位置を変えるためには、Addノードを用いてオフセット値を加算します。
ここで加算するオフセット値はHideOffsetとしてプロパティ化し、自由に後から変更出来るようにします。

3.3.4. 消える距離を調整できるようにする
最後に、ハーフトーンが消えるまでの距離を調整できるようにします。
以下のイメージのように、境界値を減少させる幅を指定倍数伸ばすことで調整できるようにします。

これを表現するためには、割り算のDivideノードを使用し、割られる数として新しくプロパティHideDistanceを用意します。

ここまでの配置が終わると、シェーダーをアタッチしたMaterial内のプロパティを調整することで、ある程度ハーフトーンらしい調整が可能になってきます。
3.4. 回転させる
ここからは、これまでの内容に回転の要素を入れていきます。
回転についても、これまでと同様に、UV座標に変化を加えることで表現することができます。
回転の表現には、Rotationノードを使用し、UVノードとDivideノードの中間に接続します。
また、この時Rotationの値をRotationという名前でプロパティ化し、自由に回転出来るようにします。

以上のように配置すると、以下画像のように傾いたハーフトーンを作成することが出来ます。
3.5. 移動させる
ついにハーフトーンを動かせるようにします。
ここでは、ハーフトーンを表現するために使用していたVoronoiノードのUVをスクロールすることで移動を表現します。
3.5.1. とりあえず動かす
移動を表現するためには、まずある地点のUV座標がt秒後どんな位置にあるかを考える必要があります。
始点を0として、距離をd、移動速度をv、時間をtとして表すと、
d = vt
と書きあらわすことができます。
これをグラフ上に落とし込んでいきます。
まず、Moveグループ内にて経過時間を示すTimeと、新しく作成した移動速度のプロパティMoveSpeedを掛け合わせた値を用意します。
そして、それを縦横比を維持したUV座標の出力値と加算し、VoronoiノードのUVと接続します。

以上の状態でSave Assetから保存すると、斜め方向へハーフトーンがスクロールするようになっています。
3.5.2. 移動方向を与える
現在のままでは一方向にしか移動できません。
そのため、新しいVector 2型のプロパティMoveDirectionを作成することで移動方向を与えられるようにします。
ここで注意すべきは、移動速度は既に設定されている点です。
MoveDirectionの値を掛ける際には、掛ける前に一度正規化を行い、ベクトルの大きさを1にする必要があります。
以上の内容を取り入れると、以下グラフのようになります。

Normalizeノードを使用し、移動方向を正規化したベクトルと、元々の出力とで掛け合わせています。
そして、そこで得られた出力値を新しくAddノードと繋ぎ直します。
One Minusノードについては、移動方向の調整という意味で配置しています。
ここで、MoveDirectionの値を(x, y)=(-1, 0)とすると、左方向へ移動していくことが確認できます。
3.6. テクスチャの形状に合わせる
ここまでで大方の調整は完了しました。
ここからは、取り付けているテクスチャの形状に合わせる作業を行います。
まず、テクスチャの形状や色を取得するために、Sample Texture 2Dノードを使用し、TextureにMainTexプロパティを接続します。
テクスチャ内でアルファ値が0の部分に関しては映す必要が無い透過部分のため、これまでAlphaに出力していた値とアルファチャンネルの不透明度を掛け合わせます。
以下グラフのように、アルファチャンネルとOne Minusノードの出力をMultiplyノードを使用して掛け合わせ、Alphaに繋ぎ直します。
色に関してはSample Texture 2DノードのRGBAをBase Colorに接続することで表現することが出来ます。

ここまで作成できれば、完成となります!
Save Assetから保存し、確認すると、今回の目的であったUIの形状に沿って動くハーフトーンを見ることが出来ます。

4. まとめ
以上、ShaderGraphを用いた、動く2D向けハーフトーンの制作の手順紹介でした。
ここまでの手順をざっくりとまとめます。
-
Voronoiノードを使用して、ハーフトーンの形を作る -
Texel Sizeノードをもとに、縦横比を維持できるようにする -
UV座標に応じてマルを小さくする -
UVを回転させる -
UVスクロールで移動させる -
Sample Texture 2Dノードで形状や見た目を整える
5. あとがき
ここまで記事を読んでくださり、ありがとうございました。
今回作成したシェーダーは、UIのアクセントや装飾としてのみならず、画面遷移などの場面でも活用できる余地がありそうですね。
また、今回のハーフトーンは縦横が揃ったものになっていますが、マルの位置が段ごとに交互になっているようなハーフトーンを作ってみるのも面白いかもしれません。
これは今後の課題にしたいと思います。
筆者なりに、ある程度利用しやすいように・汎用化できるように制作しましたが、もっと様々な活用の仕方はあると思いますので、是非読者の方々もここから自由に拡張して頂ければと思います。
それでは。
参考文献







