トーンマップパスの修正
以前,トーンマップや
カラーコレクションを実装しましたが,
思うところあって,パスを次のように修正しました。
前は,パス内部で直接トーンマップ関数の計算をしたり,色行列を乗算していました。
それだと,組み合わせが膨大になり,用意するシェーダー数が膨大になることと,複雑な計算をする場合に遅くなる懸念があったので,思い切ってLUT化しています。
また,細かいところですが,アンチエイリアスをトーンマップ後のSDR空間にて適用することにしました。
ノイズやディザリングを付与する処理も,アンチエイリアス後に適用しています。これは、アンチエイリアスで細かいノイズが消されないようにするためです。
これらの実装は,UnityのPostProcessingStackと似た実装になっています。
LUTの作成
LUTは3DテクスチャのXYZの軸をRGBと読み替えてマッピングするテーブルのことですが,単純に作ってしまうと0~1の範囲の色のテーブルしか作れません。
今回は無限大の範囲まで持つHDRカラーから,0~1までのSDRカラーにマッピングする必要があります。
HDRカラーの全範囲はLUTとして記録できないので,適当な範囲を定めて圧縮することで対応します。
UnityのPostProcessingStackの場合は.Alexa LogC Curveという対数カーブでHDRカラーを圧縮しているようです。
今回は,Alexa LogCではなく,S-Log3を使って圧縮をしたいと思います。
S-Log3を選んだ理由は特にありません。強いて言えば日本語の文献が沢山あったからでしょうか。Alexa LogCでもS-Log3でも,結局はlogの積和計算で表されて係数が違うくらいなので,大きな違いは無いんじゃないかと思います。
LUTを作成するCompute Shaderは以下のようなものになりました。
float _linear2log(float x)
{
[flatten]
if (x >= 0.01125f)
{
return (420.0f+log10((x+0.01f)/(0.18f+0.01f))*261.5f)/1023.0f;
}
else
{
return(x *(171.2102946929f-95.0f)/0.01125000f+95.0f)/1023.0f;
}
}
float3 linear2log(float3 color)
{
return float3(_linear2log(color.x), _linear2log(color.y), _linear2log(color.z));
}
float _log2linear(float x)
{
[flatten]
if (x >= 171.2102946929f / 1023.0f)
{
return pow(10.0f, (x*1023.0f - 420.0f) / 261.5f) * (0.18f + 0.01f) - 0.01f;
}
else
{
return(x * 1023.0f - 95.0f) * 0.01125000f / (171.2102946929f - 95.0f);
}
}
float3 log2linear(float3 color)
{
return float3(_log2linear(color.x), _log2linear(color.y), _log2linear(color.z));
}
RWTexture3D<float4> g_DstTexture : register(u0);
[numthreads(8, 8, 8)]
void CS_Bake(uint3 position : SV_DispatchThreadID)
{
uint3 dstTextureSize;
g_DstTexture.GetDimensions(dstTextureSize.x, dstTextureSize.y, dstTextureSize.z);
float3 outColor = log2linear( saturate(position.rgb / (dstTextureSize - 1.0f)) );
outColor = Transform(COLOR_MATRIX_BEFORE, outColor);
outColor = TonemapFunction(
outColor,
TONEMAP_FUNC_TYPE,
TONEMAP_PARAMETER_1,
TONEMAP_PARAMETER_2,
TONEMAP_PARAMETER_3,
TONEMAP_PARAMETER_4,
TONEMAP_PARAMETER_5,
TONEMAP_PARAMETER_6
);
outColor = pow(outColor, INV_GAMMA);
outColor = Transform(COLOR_MATRIX_AFTER, outColor);
g_DstTexture[position] = float4(saturate(outColor), 0.0f);
}
LUTを作る段階で,カラーコレクションやガンマの調整を含んでいます。
HDRからSDRへのトーンマップを行うときは,入力カラーにlinear2log()関数を適用したRGB値でLUTを引くことで実現します。
結果
Reinhard関数のLUTを作成し,トーンマップをした結果です。
LUTのサイズは64x64x64です。128にしても結果に差が出なかったので,64にしています。
ついでに,GT-Tonemapを適用してみました。
https://www.slideshare.net/nikuque/hdr-theory-and-practicce-jp
https://www.desmos.com/calculator/et1qmcg10s