3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

カバー株式会社Advent Calendar 2024

Day 19

【Unity】シェーダーの半透過ブレンドモードに関する考察

Last updated at Posted at 2024-12-23

はじめに

Unity Recorderではカメラのレンダリングの結果を画像や映像素材として出力することができます。このとき半透過のオブジェクトがある場合、出力されるレンダリング結果はシェーダーのブレンドモードで挙動が変化します。ここではUnity Recorderと半透過にまつわるブレンドモードについて考察を行っていきます。

ブレンド係数

UnityのShaderLabにはシェーダーで出力する色と既に描画されているフレームバッファとの色の混ぜ具合(ブレンドモード)を指定できるコマンドがあります。

Blend SrcFactor DstFactor
Blend SrcFactor DstFactor, SrcFactorA, DstFactorA

第二引数を指定することで色とアルファそれぞれのブレンドモードを指定が可能

SrcFactorとDstFactorには以下の係数を乗算指定することができます。

係数 機能
Off ブレンドの無効化
One 1
Zero 0
SrcColor シェーダーのColor
SrcAlpha シェーダーのAlpha
DstColor フレームバッファのColor
DstAlpha フレームバッファのAlpha
OneMinusSrcColor 1 - Source Color
OneMinusSrcAlpha 1 - Source Alpha
OneMinusDstColor 1 - Destination Color
OneMinusDstAlpha 1 - Destination Alpha

これらの係数を組み合わせることで最終的な出力値が決まります。

finalValue = sourceFactor * sourceValue operation destinationFactor * destinationValue
  • sourceValue: シェーダーで計算した色
  • destinationValue: フレームバッファの色
  • opreraton: ブレンドの操作方法

ブレンド操作

BlendOp OpColor
BlendOp OpColor, OpAlpha // ColorとAlpha別々で指定する場合

ブレンド係数と同様に第二引数を指定することでColorとAlphaそれぞれのoperationを指定できます。operationの代表的なものとして以下があります。

operation 機能
Add sourceとdestinationを加算(デフォルト)
Sub sourceからdestinationを減算
RevSub destinationからsourceを減算
Min sourceとdestinationの小さい方を使用
Max sourceとdestinationの大きい方を使用

特にBlendOpを指定しない場合はAddが適用され、最終的な出力式は以下のようになります。

finalValue = sourceFactor * sourceValue + destinationFactor * destinationValue

ブレンドモードによる半透過描画

ブレンド操作をおさらいしたところで早速、ブレンドモードの組み合わせをみていきます。以下のシェーダーを使ってオブジェクトをシーン上に適当に配置します。

Shader "Unlit/BlendOp"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha
//        Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha
//        Blend SrcAlpha OneMinusSrcAlpha, One One
//        BlendOp Add, Max
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

GameView

GameView_SrcAlpha_OneMinusSrcAlpha_One_One.png

SceneView

スクリーンショット-2024-12-22-220922.jpg

カメラを基準に手前から奥に①→②→③→④の順に配置しています。Alpha値はそれぞれ0.4とし、黄色のみ1.0(不透過)としてあります。括弧は(R, G, B, A)

Blend SrcAlpha OneMinusSrcAlpha

一般的な半透過のブレンドモードで、ColorとAlphaは同じ式でブレンドされます。
(AlphaはSrcAlpha * sourceValueの項があるため線形補間とならない)

finalValue = SrcAlpha * sourceValue + (1 - SrcAlpha) * destinationValue

GameView

GameView_SrcAlpha_OneMinusSrcAlpha.png
描画は問題なし

Unity Recorder PNG出力

ps_SrcAlpha_OneMinusSrcAlpha.jpg

重なっている部分のAlpha値を計算してみます。

1枚:0.4\times 0.4+( 1-0.4) \times 0=0.16\ (16\%)
2枚:0.4\times 0.4+( 1-0.4) \times 0.16 \fallingdotseq 0.25\ (25\%)
3枚:0.4\times 0.4+( 1-0.4) \times 0.25 \fallingdotseq 0.31\ (31\%)
1枚+不透明:0.4\times 0.4+( 1-0.4) \times 1 = 0.76\ (76\%)

出力されたPNG画像をPhotoShop等で開いてAlpha値を確認すると計算値と一致していました。Blend SrcAlpha OneMinusSrcAlphaのときは不透過と重なる部分が透けてしまっていることがわかります。

Blend SrcAlpha OneMinusSrcAlpha / BlendOp Add, Max

半透過のブレンドモードに加え、Alphaはsourceとdestinationの大きい方を使用するパターンです。

finalValue = SrcAlpha * sourceValue + (1 - SrcAlpha) * destinationValue
BlendOp Add, Max

GameView_SrcAlpha_OneMinusSrcAlpha_BlendOp_Add_Max.png
描画は問題なし

ps_SrcAlpha_OneMinusSrcAlpha_BlendOp_Add_Max.jpg

0.4 = 0.4 \rightarrow 40\%
0.4 < 1.0 \rightarrow 100\%

不透過と重なる部分は大きいAlpha値が優先されるため透けずに出力されていることがわかります。ただし、半透過で重なる部分は全て同じ値Alpha値となっています。(半透過の3枚は同じAlpha値のため)

Blend SrcAlpha OneMinusSrcAlpha, One One

Colorは線形補間、AlphaはsourceAlphaとdestinationAlphaの加算でブレンドします。

finalColor = SrcAlpha * sourceValue + (1 - SrcAlpha) * destinationValue
finalAlpha = 1 * sourceAlpha + 1 * destinationAlpha

GameView_SrcAlpha_OneMinusSrcAlpha_One_One.png
描画は問題なし

ps_SrcAlpha_OneMinusSrcAlpha_One_One.jpg

1枚:1 \times 0.4 + 1 \times 0 = 0.4\ (40\%)
2枚:1 \times 0.4 + 1 \times 0.4 = 0.8\ (80\%)
3枚:1 \times 0.4 + 1 \times 0.8 = 1.2 \rightarrow 1.0\ (100\%)
1枚+不透明:1 \times 0.4 + 1 \times 1.0 = 1.4 \rightarrow 1.0\ (100\%)

3枚重なる部分と不透過と半透過が重なる部分のAlpha値は1を超えますが1でクリップされます。

Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha

ColorとAlphaをSrcAlphaで線形補間します。

finalColor = SrcAlpha * sourceValue + (1 - SrcAlpha) * destinationValue
finalAlpha = 1 * sourceAlpha + 1 * destinationAlpha

GameView_SrcAlpha_OneMinusSrcAlpha_One_OneMinuSrcAlpha.png
描画は問題なし

ps_SrcAlpha_OneMinusSrcAlpha_One_OneMinuSrcAlpha.jpg

1枚:1\times 0.4+( 1-0.4) \times 0=0.4\ (40\%)
2枚:1\times 0.4+( 1-0.4) \times 0.4 = 0.64\ (64\%)
3枚:1\times 0.4+( 1-0.4) \times 0.64 \fallingdotseq 0.78\ (78\%)
1枚+不透明:1\times 0.4+( 1-0.4) \times 1 = 1.0\ (100\%)

まとめ

今回試したブレンドモードのパターン間で描画の差はありませんでしたが、Unity Recorderの出力結果には差が確認できました。どのブレンドモードが正しいというものではなく、求める用途や表現によってブレンドモードを適切に使い分けることが大切であると言えるでしょう。

Unity内で完結する描画ではあまり半透過のブレンドはそこまで意識する機会は少なそうですが、Unityの描画出力を素材として扱う場合は重要になっていることが知見として得られました。もし半透過ブレンドでお困りの際はこの記事が少しでも参考になれば幸いです。🌱

参考リンク

https://docs.unity3d.com/ja/2019.4/Manual/SL-Blend.html
https://docs.unity3d.com/Manual/SL-Blend.html
https://github.com/Santarh/MToon/issues/10
https://discussions.unity.com/t/dstalpha-alpha-blendmode-alpha-blendop/694629/2

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?