動画用に軽量色補正シェーダーを作る

7 min read読了の目安(約6800字

はじめに

Unityで180/360度動画を色補正をしたくてPost Processing Stack v2(PPS)のColor Gradingを使ったのですが動画の解像度が大きいとコマ落ちしてしまうのでその対処方法を紹介します。

White BalanceのTemperatureのみ変化させると以下の動画のようになります。

Unity内で再生している動画はCCライセンスで提供されているBig Buck Bunny[1]です。

方針

PPSが遅いのは一度描画した内容を加工するからなので、動画を表示するメッシュのみ加工すれば軽くなるはずです。そのためにPPSの色補正をフラグメントシェーダーに移植します。

PPSのコードは以下のUnity Companion License[2]、Built in ShadersはMIT License[3]で提供されています。

White Balanceを移植する

PPSのWhite Balance

PPSのColor GradingのうちWhite Balanceを移植してみます。

PPS

TemperatureTintColorUtilities.ComputeColorBalanceの結果をシェーダープロパティに渡しています。

ColorGrading.cs
var colorBalance = ColorUtilities.ComputeColorBalance(settings.temperature.value, settings.tint.value);
cmd.SetComputeVectorParam(compute, "_ColorBalance", colorBalance);

https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.postprocessing/PostProcessing/Runtime/Effects/ColorGrading.cs#L484-L485

次にシェーダープロパティの_ColorBalanceWhiteBalance関数で係数を計算して色に乗算しています。

Lut2DBaker.shaer
colorLinear = WhiteBalance(colorLinear, _ColorBalance);
colorLinear *= _ColorFilter;

https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.postprocessing/PostProcessing/Shaders/Builtins/Lut2DBaker.shader#L39-L40

そしてWhiteBalance関数は以下の構成です。

Color.hlsl
//
// White balance
// Recommended workspace: ACEScg (linear)
//
static const float3x3 LIN_2_LMS_MAT = {
    3.90405e-1, 5.49941e-1, 8.92632e-3,
    7.08416e-2, 9.63172e-1, 1.35775e-3,
    2.31082e-2, 1.28021e-1, 9.36245e-1
};

static const float3x3 LMS_2_LIN_MAT = {
    2.85847e+0, -1.62879e+0, -2.48910e-2,
    -2.10182e-1,  1.15820e+0,  3.24281e-4,
    -4.18120e-2, -1.18169e-1,  1.06867e+0
};

float3 WhiteBalance(float3 c, float3 balance)
{
    float3 lms = mul(LIN_2_LMS_MAT, c);
    lms *= balance;
    return mul(LMS_2_LIN_MAT, lms);
}

https://github.com/Unity-Technologies/Graphics/blob/master/com.unity.postprocessing/PostProcessing/Shaders/Colors.hlsl#L494-L515

フラグメントシェーダーに移植する

WhiteBalance関数の構成が分かったのでこれを動画を表示するシェーダーに移植します。
今回はUnlit/Textureに用います。

Unlit/Textureを取得する

以下の使っているUnityのバージョンのBuilt in Shadersからzipをダウンロードします。
これを展開したDefaultResourcesExtra/Unlit/Unlit-Normal.shaderがUnlit/Textureです。

https://unity3d.com/get-unity/download/archive
Built in Shader

Unlit/TextureにWhiteBalanceを移植する

以下が変更内容と完成したシェーダーです。

  1. Propertiesと変数に_ColorBalanceを追加
  2. WhiteBalance関数をコピー
  3. フラグメントシェーダーでWhiteBalance関数を適用
Unlit-Normal.shader
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

// Unlit shader. Simplest possible textured shader.
// - no lighting
// - no lightmap support
// - no per-material color

Shader "Unlit/Texture/WhiteBalance" {
Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
// ここから1
    _ColoBalance ("Color Balance", Vector) = (1,1,1,1)
// ここまで1
}

SubShader {
    Tags { "RenderType"="Opaque" }
    LOD 100

    Pass {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

            struct appdata_t {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f {
                float4 vertex : SV_POSITION;
                float2 texcoord : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                UNITY_VERTEX_OUTPUT_STEREO
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
// ここから2
            float4 _ColorBalance;

            //
            // White balance
            // Recommended workspace: ACEScg (linear)
            //
            static const float3x3 LIN_2_LMS_MAT = {
                3.90405e-1, 5.49941e-1, 8.92632e-3,
                7.08416e-2, 9.63172e-1, 1.35775e-3,
                2.31082e-2, 1.28021e-1, 9.36245e-1
            };

            static const float3x3 LMS_2_LIN_MAT = {
                2.85847e+0, -1.62879e+0, -2.48910e-2,
                -2.10182e-1,  1.15820e+0,  3.24281e-4,
                -4.18120e-2, -1.18169e-1,  1.06867e+0
            };

            float3 WhiteBalance(float3 c, float3 balance)
            {
                float3 lms = mul(LIN_2_LMS_MAT, c);
                lms *= balance;
                return mul(LMS_2_LIN_MAT, lms);
            }
// ここまで2

            v2f vert (appdata_t v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.texcoord);
                UNITY_APPLY_FOG(i.fogCoord, col);
                UNITY_OPAQUE_ALPHA(col.a);
// ここから3
                fixed3 c = WhiteBalance(col, _ColorBalance);
                return fixed4(c, col.a);
// ここまで3
            }
        ENDCG
    }
}
}

スクリプトから色補正する

以下のようにtemperatureとtintをColorUtilities.ComputeColorBalanceで計算した値を_ColorBalanceに渡すと色補正されます。

WhiteBalanceUI.cs
using UnityEngine;
using UnityEngine.Rendering.PostProcessing;
using UnityEngine.UI;

public class WhiteBalanceUI : MonoBehaviour
{
    [SerializeField] private Slider temperatureSlider;
    [SerializeField] private Slider tintSlider;
    [SerializeField] private Renderer videoRenderer;
    private Material mat;
    private static readonly int ColorBalance = Shader.PropertyToID("_ColorBalance");

    private void Start()
    {
        mat = videoRenderer.material;
        temperatureSlider.onValueChanged.AddListener(OnMoveSlider);
        tintSlider.onValueChanged.AddListener(OnMoveSlider);
    }

    private void OnDestroy()
    {
        temperatureSlider.onValueChanged.RemoveListener(OnMoveSlider);
        tintSlider.onValueChanged.RemoveListener(OnMoveSlider);
    }

    private void OnMoveSlider(float _)
    {
        ChangeWhiteBalance(temperatureSlider.value, tintSlider.value);
    }

    private void ChangeWhiteBalance(float temperature, float tint)
    {
        if (mat)
        {
            var whiteBalance = ColorUtilities.ComputeColorBalance(temperature, tint);
            mat.SetVector(ColorBalance, whiteBalance);
        }
    }
}
脚注
  1. Big Buck Bunny ↩︎

  2. Unity Companion License ↩︎

  3. The MIT License ↩︎