RICHO THETA
360度カメラである,THETA SCを買いました。
解像度は静止画で5376x2688で,だいたい4K相当です。
マルチブラケット撮影もできるので,Photomatix ProみたいなHDRIの合成ソフトを使ってみたら,すぐHDRIができました。
http://cgcompo.blog134.fc2.com/blog-entry-67.html
https://www.hdrsoft.com/jp/
ただ,便利なソフトを使ってみただけでは面白くないので自分でHDRIの合成をしてみたいと思います。
HDRIの作成
まずは,以下のように複数の露光でマルチブラケット撮影をしておきます。
THETAは絞りが弄れない関係上,どうしても明るく撮れてしまうので,木陰など暗めの場所で撮ったほうがよいと思いました。
また,撮影時間も30~40秒ぐらいはかかるので,三脚とシータ棒は必須です。
次に,各画像を取り込んでシェーダーでRGBA16Fフォーマットのテクスチャへ合成します。
THETAは残念ながらRAW画像は取得できないので,JPEGとして読み込みます。
画像を取り込むときには,EXIF情報も同時に読んで,露光時間とF値,ISO感度からEV値を計算しておきます。
画像の読み込みはDirectXTexを利用しました。EXIF情報はIWICMetadataQueryReaderから読んでいます
https://github.com/Microsoft/DirectXTex
https://msdn.microsoft.com/ja-jp/library/windows/desktop/ee719904(v=vs.85).aspx
hr = DirectX::LoadFromWICFile(wcFilePath, flags, &metadata, scratchImage,
[&](IWICMetadataQueryReader* pReader)
{
PROPVARIANT value;
PropVariantInit(&value);
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=33434}", &value)))
{
NGFX_ASSERT(value.vt == VT_UI8);
exifDesc.ExposureTime = reinterpret_cast<EXIF_RATIONAL*>(&value.uhVal)->ToFloat();
}
PropVariantClear(&value);
PropVariantInit(&value);
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=33437}", &value)))
{
NGFX_ASSERT(value.vt == VT_UI8);
exifDesc.FNumber = reinterpret_cast<EXIF_RATIONAL*>(&value.uhVal)->ToFloat();
}
PropVariantClear(&value);
PropVariantInit(&value);
if (SUCCEEDED(pReader->GetMetadataByName(L"/app1/ifd/exif/{ushort=34855}", &value)))
{
NGFX_ASSERT(value.vt == VT_UI2);
exifDesc.ISO = value.iVal;
}
PropVariantClear(&value);
});
入力画像を1枚のHDRIとして合成するときには,EV値が高い画像ほど,値としては暗い部分になるようにスケールします。
いろいろ合成のやり方はあると思いますが,今回は三角形型の重み関数で合成を行っています。
合成を行う時に,ソースとなる画像のあまりに暗すぎる部分や明るすぎる部分を計算から省くような処理を行っています。
matlabのドキュメントでも似たようなことをしているようです。
https://jp.mathworks.com/help/images/ref/makehdr.html
実際の合成を行うシェーダーは以下。
Texture2D g_aSrcTexture[TEXTURE_NUM] : register(t0);
SamplerState g_Sampler : register(s0);
#define MIN_LIMIT (g_aTemp[0].x)
#define MAX_LIMIT (g_aTemp[0].y)
#define SRC_INV_EXPOSURE(index) (((float[TEMPCB_ARRAY_SIZE*4])g_aTemp)[(index)+4])
float Weight(float c)
{
const float w1 = saturate((c - MIN_LIMIT) / (0.5f - MIN_LIMIT));
const float w2 = saturate(1.0f - (c - (MAX_LIMIT - 0.5f)) / (MAX_LIMIT - 0.5f));
float w = (c < 0.5f) ? (w1) : (w2);
w = max(w, 0.0001f);
return w;
}
float4 PS_Composite(PS_IN In) : SV_TARGET0
{
const float2 uv = In.UV;
float4 outColor = 0.0f;
float3 totalWeight = 0.0f;
[unroll]
for(uint id=0;id<TEXTURE_NUM;++id)
{
float3 srcColorSDR = SampleTextureLevel0(g_aSrcTexture[id], g_Sampler, uv).rgb;
const float3 srcColorHDR = srcColorSDR * SRC_INV_EXPOSURE(id);
float3 weight = 1.0f;
weight.x = Weight(srcColorSDR.x);
weight.y = Weight(srcColorSDR.y);
weight.z = Weight(srcColorSDR.z);
totalWeight += weight;
outColor.rgb += weight * srcColorHDR;
}
outColor.rgb /= totalWeight;
return outColor;
}
結果
出来上がったHDRIを使って,レンダリングをしてみました。
中央の球体のレンダリングはUnrealEngineのPBRを実装しました。
http://blog.selfshadow.com/publications/s2013-shading-course/
ちゃんと高輝度成分が入っているので,ブルームエフェクトも機能しています。
HDRI合成の重みを工夫したらもっとキレイにできそうです。