モバイルでのHDRテクスチャの扱い
Unity で HDRテクスチャが使用できることは存外知られていません。知っている,という人もせいぜい IBR の空に使うくらいではないでしょうか。
https://gyazo.com/f4c84f8c3081542c13a7e3eccfd3117b
HDRテクスチャによるレンダリング例。色が同じで輝度が違う。
ここで言うHDRテクスチャとは,RGBチャンネルそれぞれ,あるいは輝度チャンネルにおいて8ビット(256段階)を超えるダイナミックレンジを扱うことのできるテクスチャフォーマットのことを指します。Unity は RGBE形式(拡張子.hdr)もしくは OpenEXR(拡張子.exr)のものをインポートすることができます。
インポートされたHDRテクスチャは,プラットフォームが対応していればHDRに対応した内部形式(例えばBC6H)に変換され,特にユーザーが意識することなく扱うことができます。しかし,モバイルなどネイティブでHDRに対応していないプラットフォームでは,シェーダーに対応コードを追加する必要があります。ここでは,その手法について説明します。
古い(Unity5.3までの)方法
以前,UnityはHDRテクスチャをRGBM形式でインポートすることが可能でした。テクスチャのインポート設定パネルにかつて存在した「Import as RGBM」のチェックボックスを覚えている方も多いのではないでしょうか。RGBM形式はアルファチャンネルにRGBチャンネルに乗ずるべき値の1/8が格納されており,シェーダーでは以下のコードで元の値を復元することができました。
color.rgb *= color.a * 8;
今でもネットで調べるとこの方法でHDRテクスチャをデコードできると書いてあるページが引っかかりますが,この方法はもはや使ってはいけません。
新しい(Unity5.4以降の)方法
何が変わったのでしょうか。それは一言で表すと「設定が自動化された」ことです。古い方法ではユーザーが「これはHDRテクスチャである」とインポーターに指示する必要がありました。また,HDRテクスチャである場合は異なるシェーダーを用意して明示的にデコードする必要がありました。この設定やシェーダーの組み合わせを間違えると正しい絵が出ません。
翻って現在ではユーザーがテクスチャがHDRであるか否かを意識する必要がなくなりました。テクスチャは自動的に適切な内部形式に変換され,シェーダーは必要であればこれを自動的にデコードします。ただし,そのためには正しくシェーダーを書かなければなりません。しかし残念ながら現時点ではHDRに対応したシェーダーはUnity標準ではほとんど提供されておらず,情報が不足している状況です。
HDR対応シェーダーコードの例
ここに,標準の "Unlit/Texture" シェーダー(DefaultResourcesExtra/Unlit/Unlit-Normal.shader)をHDR対応に改造したシェーダーコードを掲載します。
code:Unlit-HDR.shader(c)
Shader "Unlit/HDR" {
Properties {
HDR _MainTex ("Base (RGB)", 2D) = "white" {} }
SubShader {
Tags { "RenderType"="Opaque" }
LOD 100
Pass {
CGPROGRAM
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;
float4 _MainTex_HDR;
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);
col.rgb = DecodeHDR(col, _MainTex_HDR);
UNITY_APPLY_FOG(i.fogCoord, col);
UNITY_OPAQUE_ALPHA(col.a);
return col;
}
ENDCG
}
}
}
以下が変更した箇所です。一つづつ見ていきます。
3: [HDR] _MainTex ("Base (RGB)", 2D) = "white" {}
テクスチャプロパティです。HDRに対応しているという印,[HDR]を付けます。
34:float4 _MainTex_HDR;
テクスチャのHDRに関する情報が入っているシェーダー定数です。
Unityが設定します。
50:col.rgb = DecodeHDR(col, _MainTex_HDR);
内部形式から本来の色の値をデコードしています。DecodeHDRは,UnityCG.cgincで提供されている便利関数です。
実は,やっていることはRGBM時代と変わりません。ただ,Mの倍率が8固定だったのに対し,新しいフォーマットでは8であるとは限りません。また,ネイティブでHDR形式のテクスチャに対応したプラットフォームでは,これらの追加コードは何もしませんので効率が落ちることもありませんしコードを共通化できます。
シェーダーの対応は以上です。簡単ですね?
あなたのシェーダーコードも,すぐにHDRテクスチャに対応させることができるでしょう。
その他の設定とHDRを活用した絵作り
テクスチャとシェーダーだけHDRに対応してもそのままでは何も変わりません。ざっと以下のような項目の設定を行う必要があります。
GraphicsSettingsでフレームバッファのHDRを有効にする
PlayerSettingsでHDRフレームバッファに対応したGraphics APIを使用する
カメラの設定でHDRが有効になっていることを確認する
このあたりの詳細は少し調べれば情報が得られると思いますのでここでは割愛します。
また,HDRをただ有効にするだけではなく,Post Processing Stackなどで効果的な絵作りをすることが重要です。
HDRは単なるブルームエフェクトではありません。反射や透過での強度の伝搬,露出の決定,飽和の制御,等々,HDRを使用する場合はアートワークのトータルで設計することが肝要です。
Chaola.icon