4
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?

Siv3Dでカラーグレーディング(LUT)

Last updated at Posted at 2025-12-05

はじめに

カラーグレーディングとは

一言で言うと
色合いを調整するポストプロセス

コントラストや彩度などのパラメータ調整をして扱うケースも多いですが
今回はシンプルに Lookup Table (LUT) (もしくはLookup Textureといったりする) を使って色補正を行う実装をしてみる

LUTとは

色を別の色に変換する3次元テーブルのプリセット
概念的に3次元テクスチャを扱う

「概念的には」という言い方をしたのは
実際には2次元テクスチャで持つ場合も多いからである

.cubeなどのフォーマットで3次元データとして扱うことも可能だが
少なくとも、Siv3DではTexture3Dを扱えないので、.pngなどの画像フォーマットで2次元データで持つ

lut.png (32 * 32, 32)
lut.png
↑こんな感じで、32x32の画像を32個横にならべることで32x32x32の3次元データを表現している

2次元テクスチャのデータは、縦横(xy)の2次元座標でピクセルの色が決まるのに対して
3次元テクスチャのデータは、縦横奥(xyz)の3次元座標でピクセルの色が決まる

LUTを使った色変換について

具体的にどのように色変換を行うかというと、とても単純である。

入力色として (r, g, b) の3パラメータがある
LUTは3次元データなので、上記のrgbをLUTのuvw(xyz)座標に割り当てることで、ピクセル座標の色が決定し
それがそのまま出力色になる

image.png

シェーダーコードにすると以下のようになります

// inColorのrgbをuvwにして色をサンプリング;
float3 outColor = lut3D.Sample(g_sampler0, inColor.rgb).rgb;

LUTを作るには?
Photshopなどで作れるらしい。
サンプルのLUTはSiv3Dで作った。

Siv3D用のシェーダーを書く

Texture3Dの場合何も考えなくてもいいが
2Dでやる場合、奥行を横に並べて表現した関係で
リニアサンプリングの補間処理などを考慮すると、こまごま対応がいるのは注意
(w方向の補間対応やz座標が変わる境界など)

color_grading.hlsl
Texture2D g_texture0 : register(t0);
Texture2D g_lut : register(t1);
SamplerState g_sampler0 : register(s0);

namespace s3d
{
    struct PSInput
    {
        float4 position : SV_POSITION;
        float4 color : COLOR0;
        float2 uv : TEXCOORD0;
    };
}

cbuffer PSConstants2D : register(b0)
{
    float4 g_colorAdd;
    float4 g_sdfParam;
    float4 g_sdfOutlineColor;
    float4 g_sdfShadowColor;
    float4 g_internal;
}

// LUTを使ったカラーグレーディング
float3 ColorGradingLUT(float3 color)
{
    // 3Dテクスチャを使える場合これだけの事
    // tex3D.Sample(g_sampler0, color.rgb); 
    
    // LUTのサイズを取得
    uint width, height;
    g_lut.GetDimensions(width, height);
    float size = height;
    float lutMaxIndex = size - 1;
 
    // w方向のスライス
    float wSlice = color.b * lutMaxIndex;
    // w方向にLinear補間をかけるために2ピクセルのサンプルをする
    float wSlice0 = floor(wSlice);
    float wSlice1 = min(wSlice0 + 1, lutMaxIndex);
    float wF = wSlice - wSlice0;
    
    // ピクセル中心で計算
    float u0 = (color.r * lutMaxIndex + 0.5 + wSlice0 * size) / width;
    float u1 = (color.r * lutMaxIndex + 0.5 + wSlice1 * size) / width;
    float v = (color.g * lutMaxIndex + 0.5) / height;
    
    float3 lut0 = g_lut.SampleLevel(g_sampler0, float2(u0, v), 0).rgb;
    float3 lut1 = g_lut.SampleLevel(g_sampler0, float2(u1, v), 0).rgb;
    
    // w方向にLinear補間
    return lerp(lut0, lut1, wF);
}

float4 PS(s3d::PSInput input) : SV_TARGET
{
    // 元の色
    float4 texColor = g_texture0.Sample(g_sampler0, input.uv);
    
    // 色補正
    texColor.rgb = ColorGradingLUT(texColor.rgb);
    return (texColor * input.color) + g_colorAdd;
}

C++コードの実装

Main.cpp
# include <Siv3D.hpp>

void Main()
{
	const Texture windmill{ U"example/windmill.png" };
	const Texture lut{ U"lut.png" };
	const PixelShader ps = HLSL{ U"color_grading.hlsl", U"PS" };

	Window::Resize(Size{windmill.width() * 2, windmill.height()});
	while (System::Update()) {
		{
			ScopedCustomShader2D scopedPs(ps);

			Graphics2D::SetPSTexture(1, lut);
			windmill.draw();
		}
		{
			windmill.draw(windmill.width(), 0);
		}
	}
}

image.png

左:LUTで色補正後
右:元画像

色補正にわざわざLUTを使わなくてもよい

さて、LUTを使ってカラーグレーディングの色補正をしてみましたが、
そもそも色をちょっと補正するだけなら、シェーダーで直接色の補正コードを書いてしまうのも手です。

プログラムで書くには複雑な色補正をしたい場合や、プリセット化することで処理を共通化したい場合などはLUTを使うのも選択肢でしょう。

3次元テクスチャはサイズが大きくなるとそれなりにメモリも使うので注意がいります。

まとめ

  • LUTを使ったカラーグレーディングをSiv3Dでやってみた
  • カラーグレーディングは色味の調整するポストプロセス
  • LUTは色の変換を行う3次元テーブル
    • 元の色のrgbを入力値としてテーブル内のピクセルを特定して、出力色とする
    • フォーマットとしては2Dテクスチャで扱う事も多い
      • シェーダーでサンプリングする際はw周りに注意
  • そもそも簡易的な色補正はLUTなど使わなくてよい
4
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
4
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?