🎥
動画用に軽量色補正シェーダーを作る
はじめに
Unityで180/360度動画を色補正をしたくてPost Processing Stack v2(PPS)のColor Gradingを使ったのですが動画の解像度が大きいとコマ落ちしてしまうのでその対処方法を紹介します。
White BalanceのTemperatureのみ変化させると以下の動画のようになります。
方針
PPSが遅いのは一度描画した内容を加工するからなので、動画を表示するメッシュのみ加工すれば軽くなるはずです。そのためにPPSの色補正をフラグメントシェーダーに移植します。
White Balanceを移植する
PPSのWhite Balance
PPSのColor GradingのうちWhite Balanceを移植してみます。
Temperature
とTint
はColorUtilities.ComputeColorBalance
の結果をシェーダープロパティに渡しています。
ColorGrading.cs
var colorBalance = ColorUtilities.ComputeColorBalance(settings.temperature.value, settings.tint.value);
cmd.SetComputeVectorParam(compute, "_ColorBalance", colorBalance);
次にシェーダープロパティの_ColorBalance
はWhiteBalance
関数で係数を計算して色に乗算しています。
Lut2DBaker.shaer
colorLinear = WhiteBalance(colorLinear, _ColorBalance);
colorLinear *= _ColorFilter;
そして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);
}
フラグメントシェーダーに移植する
WhiteBalance関数の構成が分かったのでこれを動画を表示するシェーダーに移植します。
今回はUnlit/Textureに用います。
Unlit/Textureを取得する
以下の使っているUnityのバージョンのBuilt in Shadersからzipをダウンロードします。
これを展開したDefaultResourcesExtra/Unlit/Unlit-Normal.shader
がUnlit/Textureです。
Unlit/TextureにWhiteBalanceを移植する
以下が変更内容と完成したシェーダーです。
- Propertiesと変数に
_ColorBalance
を追加 - WhiteBalance関数をコピー
- フラグメントシェーダーで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);
}
}
}
Discussion