個人開発者のブログ
Unity開発者の皆さんであれば、ゲーム画面の「明るさ」を数値として正確に把握したいというニーズに直面したことがあるのではないでしょうか。
例えば、カメラが捉えている範囲内のゲーム画面の明るさを取得し、それに応じて自動露出(Auto Exposure)を調整したり、イベントを呼び出したりといったケースです。
具体的には、RenderTextureというUnityの機能を利用して、カメラが描画した結果を一旦テクスチャに落とし込み、そのテクスチャの平均輝度を計算して実装します。
目次
-
メインコード
-
詳細解説と使い方例
-
まとめ
メインコード
それでは、RenderTextureの平均輝度を計算するためのメインとなるC#コード、LuminanceUtilityクラスをご紹介します。
このコードは、パフォーマンスに優れたCompute Shader(GPU)による計算を優先しつつ、非対応環境向けにCPUによるフォールバック処理も備えた設計となっています。
using UnityEngine; /// <summary> /// RenderTextureの平均輝度を計算するユーティリティクラス /// </summary> public static class LuminanceUtility { // Compute Shader用の参照(Inspector等で設定する場合は別途管理クラスが必要) private static ComputeShader luminanceShader; /// <summary> /// RenderTextureの平均輝度を計算します /// </summary> /// <param name="renderTexture">計算対象のRenderTexture</param> /// <returns>平均輝度値(0.0~1.0)。エラー時は-1.0を返す</returns> public static float CalculateAverageLuminance(RenderTexture renderTexture) { // 入力検証 if (renderTexture == null || !renderTexture.IsCreated()) { Debug.LogError("RenderTextureが無効です。"); return -1.0f; } // Compute Shaderが利用可能な場合はGPU計算を使用 if (SystemInfo.supportsComputeShaders && luminanceShader != null) { return CalculateAverageLuminanceGPU(renderTexture); } else { // フォールバック: CPU計算 return CalculateAverageLuminanceCPU(renderTexture); } } /// <summary> /// Compute Shaderを使用した高速なGPU計算 /// </summary> private static float CalculateAverageLuminanceGPU(RenderTexture renderTexture) { // Compute Shader実装の概要: // 1. RenderTextureを入力テクスチャとして設定 // 2. 並列リダクションで輝度合計を計算 // 3. ComputeBufferから結果を読み出し // 4. ピクセル数で割って平均を算出 int kernelIndex = luminanceShader.FindKernel("CSMain"); // バッファ準備 int width = renderTexture.width; int height = renderTexture.height; ComputeBuffer resultBuffer = new ComputeBuffer(1, sizeof(float)); // Compute Shader設定 luminanceShader.SetTexture(kernelIndex, "InputTexture", renderTexture); luminanceShader.SetBuffer(kernelIndex, "ResultBuffer", resultBuffer); luminanceShader.SetInt("Width", width); luminanceShader.SetInt("Height", height); // 実行(スレッドグループ数は適切に計算) int threadGroupsX = Mathf.CeilToInt(width / 8.0f); int threadGroupsY = Mathf.CeilToInt(height / 8.0f); luminanceShader.Dispatch(kernelIndex, threadGroupsX, threadGroupsY, 1); // 結果取得 float[] results = new float[1]; resultBuffer.GetData(results); resultBuffer.Release(); return results[0] / (width * height); } /// <summary> /// CPUベースの計算(フォールバック用) /// </summary> private static float CalculateAverageLuminanceCPU(RenderTexture renderTexture) { // 一時的なTexture2Dを作成してピクセルデータを読み出し RenderTexture.active = renderTexture; Texture2D tempTexture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGBAFloat, false); tempTexture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); tempTexture.Apply(); RenderTexture.active = null; // 全ピクセルの輝度を計算 Color[] pixels = tempTexture.GetPixels(); float totalLuminance = 0f; foreach (Color pixel in pixels) { // ITU-R BT.709 標準の輝度計算式 // Y = 0.2126R + 0.7152G + 0.0722B float luminance = 0.2126f * pixel.r + 0.7152f * pixel.g + 0.0722f * pixel.b; totalLuminance += luminance; } // クリーンアップ Object.Destroy(tempTexture); // 平均輝度を計算 return totalLuminance / pixels.Length; } /// <summary> /// Compute Shaderを設定(初期化時に呼び出し) /// </summary> public static void SetComputeShader(ComputeShader shader) { luminanceShader = shader; } }
詳細解説と使い方
先ほどご紹介したLuminanceUtilityクラスは、単なる輝度計算の機能を提供するだけでなく、堅牢性とパフォーマンスを両立させた設計です。
ここでは、そのコードの核心部分を深掘りし、なぜこのような実装なのかを解説していきます。
クラス設計: なぜstaticなのか?
このクラスをstaticとして宣言するのは、
状態を持たないユーティリティ機能に徹しているからです。
・インスタンス化不要:
new LuminanceUtility()と書く必要がなく、どこからでもLuminanceUtility.CalculateAverageLuminance()と直接呼び出せます。
・メモリ効率:
インスタンスを生成するためのオーバーヘッドや、ガベージコレクションの対象となるオブジェクトを増やさないため、メモリ効率が良くなります。
・単一責任の原則:
輝度計算という一つのタスクに特化することで、コードの可読性と保守性が向上します。
この設計は、ゲームのコアロジックとは切り離された、汎用的なツールとして機能させるための最善の選択と言えるでしょう。
処理の分岐: GPU vs CPUのハイブリッド
if (SystemInfo.supportsComputeShaders && luminanceShader != null)
{
return CalculateAverageLuminanceGPU(renderTexture);
}
else
{
return CalculateAverageLuminanceCPU(renderTexture);
}
このユーティリティ最大の強みは、パフォーマンスを追求したGPU計算と、互換性を確保したCPU計算を自動で切り替えることです。
・GPU優先:
SystemInfo.supportsComputeShadersがtrue、かつ、Compute Shaderが正しく設定されていれば、超高速なCalculateAverageLuminanceGPUが実行。
・フォールバック:
Compute Shaderが非対応の古いモバイルデバイスや、何らかの理由でシェーダーの設定が漏れていた場合でも、CalculateAverageLuminanceCPUが実行され、グレースフルデグラデーション(段階的な機能低下)を実現。
これにより、「最速のパフォーマンス」と「最大限の互換性」両方を実現できます。
GPU計算: Compute Shaderによる並列処理
CalculateAverageLuminanceGPUでは、Compute Shaderを用いて輝度計算をGPU上で並列実行します。
【データ転送の準備】
ComputeBuffer resultBuffer = new ComputeBuffer(1, sizeof(float));
ComputeBufferは、GPUとCPUの間でデータをやり取りするための橋渡し役です。
ここでは、最終的な平均輝度(float値一つ)をGPUからCPUへ戻すために、要素数1、サイズ4バイト(floatのサイズ)のバッファを用意しています。
【並列実行の指示】
int threadGroupsX = Mathf.CeilToInt(width / 8.0f);
int threadGroupsY = Mathf.CeilToInt(height / 8.0f);
luminanceShader.Dispatch(kernelIndex, threadGroupsX, threadGroupsY, 1);
Compute Shaderは、スレッドグループという単位で実行されます。
UnityのCompute Shaderは、通常、[numthreads(8, 8, 1)]のように8x8のスレッドグループで定義されることが多いため、入力テクスチャの解像度を8で割り、Mathf.CeilToIntで切り上げています。これにより、テクスチャの端数部分も漏れなく処理対象に含めることができます。
【結果の取得と解放】
resultBuffer.GetData(results);
resultBuffer.Release();
return results[0] / (width * height);
GetData()でGPUからCPUへ結果を転送し、必ずRelease()でバッファを解放します。
この解放を忘れると、GPUメモリリークの原因となり、ゲームの動作が不安定になるため、非常に重要です。
最後に全ピクセルの輝度合計をピクセル総数で割ることで、正確な平均輝度を算出します。
CPU計算の詳細: 精度と人間の視覚特性
CalculateAverageLuminanceCPUは、GPUが使えない場合の最終手段です。
【RenderTextureからTexture2Dへの変換】
RenderTexture.active = renderTexture;
Texture2D tempTexture = new Texture2D(..., TextureFormat.RGBAFloat, ...);
tempTexture.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0);
// ...
GPU上のRenderTextureのデータをCPUで読み出すには、一時的にRenderTexture.activeを設定し、ReadPixels()を使ってTexture2Dにコピーする必要があります。ここで重要なのは、Texture2DのフォーマットにTextureFormat.RGBAFloatを使用している点です。
HDRレンダリング環境では、ピクセルの色値が1.0を超えることがあります。
通常のRGBA32などの整数フォーマットでは、この1.0を超える情報が切り捨てられてしまい、正確な輝度計算ができなくなります。
RGBAFloatを使うことで、高精度なHDR情報を維持したままCPUにデータを渡すことができます。
【輝度計算の国際標準: ITU-R BT.709】
float luminance = 0.2126f * pixel.r + 0.7152f * pixel.g + 0.0722f * pixel.b;
輝度(Luminance)の計算には、単にR, G, Bの平均を取るのではなく、ITU-R BT.709という国際標準規格で定められた係数が使用されています。
この係数を用いることで、単なる色の明るさではなく、人間の視覚特性に基づいた「知覚的な明るさ」に近い値を算出できるのです。
まとめ
いかがでしたか?
本記事では、Unity C#を用いてRenderTextureの平均輝度を計算し、カメラ範囲内のゲーム画面の明るさを数値化する方法について解説してきました。
是非解説した方法をプロジェクトに組み込み、画面の明るさという新たなパラメータを、ゲームデザインに活かしてみてください。
ここまで記事を見て頂き、誠にありがとうございました。
これからも開発関連の記事は更新し続けるつもりなので、今後も機会があれば見てみてください。
では、また別の記事でお会いましょう。。_(:3 」∠)_
☝Unity開発に関する最新情報をチェックしよう!
☝絵師必見!AI無断学習対策アプリ