概要
今回はグラフィックデザインの定番ツールであるPhotoshopの描画モードを、UE5のマテリアルで再現する方法について深掘りしていきます。
すでに一部の描画モードは標準機能として実装されていますが、パフォーマンスや安定性の面で使用に懸念があるケースも少なくありません。
そこで本記事では、既存の機能に頼るだけでなく、計算式を元に各描画モードを実装します。
UE5にて実装されている描画モードのMaterialFunctionは以下ディレクトリに配置されています。
/All/EngineData/Engine/Functions/Engine_MaterialFunctions03/Blends
例:エンジンに既にある焼き込みカラーのノード(Blend_ColorBurn)

計算部分を見てみるとわかりますが、ピクセル単位でゼロ除算が起こりかねない計算になっています。
ゼロ除算は数学的に結果が不定となり、予期せぬ値になることがあるので避けましょう。
またBaseの入力値が(0.0)、Blendの入力値が(0.001)だった場合、
\begin{align}
①: 1.0 - 0.0 &= 1.0\\
②: 1.0(※①) / 0.001 &= 1000\\
③: 1 - 1000(※②) &= -999\\
\end{align}
となり、結果がマイナス値となってしまいます。
中身の計算を知ったうえで使うならいいですが、知らない場合にバグのような挙動になってしまうため、出力値はなるべく標準化しておくのが望ましいですね。同様に入力値についても必ずしも0~1の値が来るとは限らないので、標準化してから計算するようにしておくと予期せぬエラーを回避できます。
入力値は信用しない。出力値の使われ方も信用しない。
開発環境
UE5.4
実装
前提
Photoshopの描画モードは、上のレイヤー(合成色)のピクセルと下のレイヤー(基本色)のピクセルの色情報を、各チャンネル(R, G, B)ごとに特定の計算式で合成し、結果色を生成します。
今回はUIやPostProcessのMaterialなど、幅広く使用できるようにMaterialFunctionで各描画モードを作成しておこうと思います。
MaterialFunctionは右クリックメニューの[マテリアル]>[高度]>[マテリアル関数]から作成できます。

作成したMaterialFunctionは[Expose to Library]をtrueにしておくと、マテリアル内右クリック時の一覧に出てくるようになるので便利です。


本文中では読みたい部分へ直接飛んで読まれることも考慮し、重複した説明を書くことがあります。ご理解ください。
明るくするグループ
比較(明)
基本色と合成色の各チャンネルを比較し、明るい方の値(大きい方)を選択します。
計算式
Result = Max(Base, Blend)
マテリアル
結果
スクリーン
基本色と合成色を反転して乗算し、さらに反転させます。重なった部分が明るくなり、黒は透明、白はそのまま白になります。
計算式
Result = 1.0 - (1.0 - Base) \times (1.0 - Blend)
マテリアル
結果
覆い焼きカラー
基本色を合成色で明るくします。コントラストが弱まり、中間調が明るくなります。黒と合成すると変化しません。
計算式
Result = \frac{Base}{1.0 - Blend}
マテリアル

ゼロ除算にならないようBの入力にMaxを挟んでいるのと、Divideの出力結果は1より大きくなる可能性があるのでSaturateで0~1の値に収めているのがポイントです。
結果
覆い焼きリニア(加算)
基本色に合成色を加算します。スクリーンよりも明るくなり、白はそのまま白、黒はそのまま黒になります。
計算式
Result = Base + Blend
マテリアル

Addの出力結果は1より大きくなる可能性があるのでSaturateで0~1の値に収めているのがポイントです。
結果
カラー比較(明)
基本色と合成色のRGB値の合計を比較し、合計値が大きい方の色を採用します。
マテリアルノードのMaxではRGB値の合計の比較ではなく、{R, G, B}それぞれを比較して大きい方の値を採用しているため、カラー比較ではやや複雑な処理が必要になります。
計算式
ちょっと複雑ですね。
これに関してはマテリアルを見たほうがわかりやすいかもしれません。
マテリアル

基本色と合成色のそれぞれの合計値にMaxをかけた値LargerValueを、それぞれの合計値から減算しています。
大きいほうの値は0.0、小さいほうの値はマイナスの値をとるため、そこに1.0を足すことで大きい方の値は1.0、小さい方の値は0.0以上1.0未満の値になります。
床関数(Floor)を使って整数部分を取得すると大きいほうの値は1.0、小さいほうの値は0.0になるので、あとは元のカラーとそれぞれを乗算して足してあげれば大きいほうの値だけが取得できます。
最後にBaseEnabledとBlendEnabledの値を足して除算しているのは、基本色と合成色が同じ値だったとき用のセーフティ処理です。
これがないと基本色と合成色が同じ場合はEnabledの値が両方とも1.0になるため、加算したときに元の色の2倍の色になってしまいます。
Enabledの値が取る範囲は1.0か2.0しかないため、Maxの処理もなく除算を入れています。
ちなみにifを使用するとシンプルに分岐できます。

筆者はできるだけ数学関数だけで処理したいという思考のもと論理演算を使って計算しています。趣味ですね。
結果
暗くするグループ
比較(暗)
基本色と合成色の各チャンネルを比較し、暗い方の値(小さい方)を選択します。
計算式
Result = Min(Base, Blend)
マテリアル
結果
乗算
基本色と合成色を掛け合わせます。重なった部分が暗くなり、白は透明、黒はそのまま黒になります。
計算式
Result = Base \times Blend
マテリアル
結果
焼き込みカラー
基本色を合成色で暗くします。コントラストが強まり、中間調が暗くなります。白と合成すると変化しません。
計算式
Result = 1.0 - \frac{1.0 - Base}{Blend}
マテリアル

ゼロ除算にならないようBの入力にMaxを挟んでいるのと、Divideの出力結果は1より大きくなる可能性があるのでSaturateで0~1の値に収めているのがポイントです。
結果
焼き込みリニア
基本色を合成色で暗くします。乗算よりも暗くなり、明るさを軽減します。
計算式
Result = Base + Blend - 1.0
マテリアル

他の計算ではAddの結果が1より大きくなる可能性があるためSaturateしていましたが、今回は最後に1を減算しているため、最終的な計算終了後にSaturateをして0~1の値に収めています。
結果
カラー比較(暗)
基本色と合成色のRGB値の合計を比較し、合計値が小さい方の色を採用します。
マテリアルノードのMinではRGB値の合計の比較ではなく、{R, G, B}それぞれを比較して小さい方の値を採用しているため、カラー比較ではやや複雑な処理が必要になります。
計算式
これまたちょっと複雑です。
計算の大筋はカラー比較(明)と同じですが、MaxではなくMinを使用する点と基本色と合成色それぞれのEnabledの取り方が異なりますね。
マテリアル
カラー比較(明)と似ているので異なる点だけ説明します。
こちらでは小さい方の値を使用したいためMaxではなくMinを使用して比較用の値SmallerValueを作ります。
それぞれの色の合計値RGBSumからSmallerValueを減算すると、小さいほうの値は0.0、大きいほうの値は0.0超過1.0以下の値になります。
この値にOneMinusした値を床関数(Floor)にかけることで小さいほうの値が1.0、大きいほうの値は0.0になりますね。
後の処理はカラー比較(明)と同じなので省略します。
結果
コントラストグループ
コントラスト系は合成色(オーバーレイのみ基本色)が0.5以下と0.5超過で別の処理をおこないます。
ノード数が増えてグラフが散らかるのを防ぐため、入力値はSaturateした値をNamedRerouteDeclarationでノード化しておきます。

また、合成値による分岐処理はStepとLerpを使用しています。

合成色を0.5をしきい値にStepで二値化し、Lerpでそれぞれの処理を繋いでいるだけです。
もちろんifを使用しても分岐可能です。筆者がifを使わないのは趣味だと思ってください。
コントラストグループのマテリアルグラフでは入力値と分岐の処理を省略しますが、どれも等しく上記の処理を入れています。
また、各描画モードの計算過程で値がマイナス値になったり1.0を超過したりしますがそれ自体は必要な工程です。
最終的な分岐処理でSaturateするので基本的に計算過程で正規化はおこないません。
オーバーレイ
明るい部分はスクリーン、暗い部分は乗算で合成します。中間調を保持しつつ、コントラストを高めます。
計算式
Result =
\begin{cases}
2.0 \times Base \times Blend & (\textbf{Base} \leq 0.5) \\
1.0 - 2.0 \times (1.0 - Base) \times (1 - Blend) & (\textbf{Base} > 0.5)
\end{cases}
オーバーレイのみ基本色(Base)の値を分岐処理の判定に使用します。
マテリアル
結果
ソフトライト
オーバーレイに似ていますが、よりソフトな効果です。コントラストが少し高まります。
計算式
Result =
\begin{cases}
2 \times Base \times Blend + Base^2 \times (1.0 - 2.0 \times Blend) & (Blend \leq 0.5) \\
2.0 \times Base \times (1.0 - Blend) + \sqrt{Base} \times (2.0 \times Blend - 1.0) & (Blend > 0.5)
\end{cases}
マテリアル
結果
ハードライト
オーバーレイと同じ計算式ですが、分岐処理の判定に基本色ではなく合成色を使います。コントラストがより強くなります。
計算式
Result =
\begin{cases}
2.0 \times Base \times Blend & (Blend \leq 0.5) \\
1.0 - 2.0 \times (1.0 - Base) \times (1 - Blend) & (Blend > 0.5)
\end{cases}
オーバーレイと計算式は同じですが、合成色(Blend)の値を条件分岐の判定に使用します。
マテリアル
結果
リニアライト
焼き込みリニアと覆い焼きリニアを組み合わせたような効果で、明るさの増減が線形的におこなわれます。
計算式
Result =
\begin{cases}
Base + 2.0 \times (Blend - 0.5) & (Blend \leq 0.5) \\
Base + 2.0 \times Blend - 1.0 & (Blend > 0.5)
\end{cases}
マテリアル
結果
ビビッドライト
焼き込みカラーと覆い焼きカラーを組み合わせたような効果で、非常に強いコントラストが生まれます。
計算式
Result =
\begin{cases}
1.0 - \frac{1.0 - Base}{2.0 \times Blend} & (Blend \leq 0.5) \\
\frac{Base}{1.0 - 2.0 \times (Blend - 0.5)} & (Blend > 0.5)
\end{cases}
マテリアル

ゼロ除算にならないようDivideの入力BにMaxを挟んでいます。
また計算過程でMaxを入れていて、一見マイナス値が失われてしまうように見えます。
しかしこの計算では事前に基本色と合成色を0.0~1.0の範囲に収めているため、Maxに入る値も0以上の値となり有効値の喪失は起こりません。
そもそも合成色の値によって使用される計算が変わるため値の範囲的にMaxは不要ですが、除算をおこなうときのマナーのようなものだと思ってください。
結果
ピンライト
基本色と合成色のどちらかの極端な値を採用します。暗い部分は比較(暗)、明るい部分は比較(明)のような効果です。
計算式
Result =
\begin{cases}
Min(Base, 2.0 \times Blend) & (Blend \leq 0.5) \\
Max(Base, 2.0 \times (Blend - 0.5)) & (Blend > 0.5)
\end{cases}
マテリアル
結果
ハードミックス
結果の色はRGBのいずれかのチャンネルが0.0または1.0(黒または白)になり、ポスタリゼーションのような効果が得られます。
ハードミックスはコントラストグループの中で唯一合成色によって分岐処理が起きない描画モードですね。
計算式
Result = \lfloor Base + Blend \rfloor
マテリアル
結果
比較グループ
差
基本色と合成色の差の絶対値を計算します。白と合成すると色が反転し、黒と合成すると変化しません。
計算式
Result = \left| Base - Blend \right|
マテリアル
結果
除外
差の絶対値に似ていますが、より柔らかい効果です。白と合成すると色が反転します。
計算式
Result = Base + Blend - 2.0 \times Base \times Blend
マテリアル
結果
減算
計算式
Result = Base - Blend
マテリアル
結果
除算
計算式
Result = Base \div Blend
マテリアル

ゼロ除算にならないようDivideの入力BにMaxを挟んでいます。
結果
おまけ
ディザ合成
ディザ合成では他の描画モードとは異なり、ピクセル値を計算して色を混ぜ合わせるのではなく、レイヤーの不透明度(Opacity)に応じてノイズ状に描画するモードです。
このモードは計算式で表せませんがマテリアルノードのNoiseを用いることでテクスチャを使わずに近しい表現ができます。
マテリアル
グラフが複雑化したので入力部と処理部を分けています。

Noiseノードをそのまま使うとかなりパターン調になる場合があるためWorldPosition部分を加工してピクセル風なノイズも選択できるようにしてあります。
また、合成色のとる色の範囲によってノイズ開始地点/終了地点を調整ができるようにStepLow、StepHighをパラメータ化してあります。
こうすることで不透明度(Opacity)を0.0~1.0でキレイにノイズ状にできます。
ノイズの開始地点/終了地点はマテリアルインスタンスで合成色を設定してから調整します。
例えば以下のようなテクスチャを基本色, 合成色にしたとします。
透明度が0.5なので合成色の文字部分がディザになるのは問題ありませんが、基本色に黒いピクセルがのってしまっています。
これはノイズの開始地点および終了地点の調整をすることで消せます。
このような調整をすることで透明度(Opacity)を0.0~1.0まで変更した際に余計なノイズが入ることを抑制できます。
結果
まとめ
こうやってみるとブレンドの種類ってたくさんありますね。
ほとんど数字の計算なので何をしている合成なのか、デザインに疎い私でも理解できてありがたい限りです。
参考















































