This is the documentation for Enlighten.
7.3.1. ディレクショナル イラディアンス
単純なイラディアンス出力を補完するもう 1 つの方法は、Enlighten に「ディレクショナル イラディアンス」を計算させることです。これは、同じイラディアンス テクスチャに 1 つまたは 3 つの追加の「ディレクショナル テクスチャ」を加えたもの指します。ディレクショナル テクスチャには、各出力ピクセルの入射光の主要な方向が含まれており、入射光が完全にアンビエントの場合はベクトル方向の長さが 0、完全に 1 つの方向に集中している場合は 1 になるよう調整されます。この光の分配に関する情報は、間接スペキュラー ハイライトの鮮明さの調整に使用できます。
ライティング モデル
ディレクショナル イラディアンス データを使用する場合、考慮すべき 2 つの法線があります。
- 「ラジオシティ法線」 — プリコンピュートで使用されるポリゴン サーフェス法線であり、実行時に適用される法線マップをすべて無視します。Enlighten のライトマップからイラディアンスが計算されるときに使用されるものです。
- 「サーフェス法線」 — ピクセルの実際のシェーディングに使用され、通常法線マップからフェッチされます。
ラジオシティ法線と変化するサーフェス法線のイラディアンス情報を両方考慮するため、Enlighten では以下の 2 つの項を組み合わせます。
- サーフェス アルベド カラーのみで近似した、サーフェスから拡散したバウンス。これは、ラジオシティ法線から外れる法線方向について、半球全体でレンダリング方程式をインテグレートすると、イラディアンスがサーフェス自体の方向になり、追加のバウンスの単純な近似が得られるからです。
- ランバート様のディレクショナル項。
Enlighten では、ディレクショナル ライティング モデルに L1 プローブ ライティングで使用されるライティング モデルと同じものを使用します。Enlighten では、ラジオシティ法線が入射光の主要な方向とどの程度整合しているかに応じて、「完全にディレクショナル」および「完全にアンビエント」の間で補間を行います。完全に整合している場合、Enlighten はライティングを完全にディレクショナルであると認識します。90 度以上の違いがある場合、Enlighten はライティングを完全にアンビエントであると認識します。この項を S と呼びます。サーフェス法線がオリジナルの「ラジオシティ法線」と完全に整合した場合にディレクショナル項が確実に 1 になるよう、Enlighten はラジオシティ法線を使用してライティング モデルを評価します。この項を R と呼びます。S を R で割ることにより、このディレクショナル モデルが Enlighten が提供するデフォルトのイラディアンス モデルと確実に整合するようにしています。
ハーフ ランバート項 H は、(1/2)*(1-cos(theta)) として計算され、theta はラジオシティ法線とシェーディング法線の間の角度です。これにより、バウンス項とディレクショナル項の寄与を決定します。最終的な結果は次のように計算されます。
I * (H * A + (1-H) * S / R)
I はイラディアンス、A はサーフェス アルベド カラーです。
ディレクショナル項は以下のように計算されます。
//--------------------------------------------------------------------------------------------------
float DirectionalInfluence(float3 direction, float3 radiosityNormal, float3 normal)
{
// 軸を外れた正接部分を再度追加することで方向にバイアスをかけて効果を倍にします
float3 tangent = direction - radiosityNormal*dot(radiosityNormal, direction);
direction = normalize(direction + tangent);
// ----- リライティング法線
// 計算された分布の中心を使ってドットの結果を計算します
float radNormDotVI = dot(radiosityNormal, direction);
float lambda = 1.0f + min(0.0f, radNormDotVI);
float q = 0.5f*(1.0f + radNormDotVI);
float q2 = q*q;
float q3 = q2*q;
float q4 = q2*q2;
float radNormVal = lambda * ((10.0f / 3.0f) * (2.f * q3 - q4) - 1.0f) + 1.0f;
// 計算された分布の中心を使ってドットの結果を計算します
float shadingNormDotVI = dot(normal, direction);
q = 0.5f*(1.0f + shadingNormDotVI);
q2 = q*q;
q3 = q2*q;
q4 = q2*q2;
float shadingNormVal = lambda * ((10.0f / 3.0f) * (2.f * q3 - q4) - 1.0f) + 1.0f;
float SdivR = shadingNormVal/radNormVal;
return SdivR;
}
Enlighten では、入射光の輝度を使って各出力ピクセルのディレクショナル イラディアンスを計算できますが、その場合は単一ディレクショナル テクスチャが生成され、以下のようにシェーダー コードに適用する必要があります。
float3 DirectionalIrradiance(float2 uv, float3 normal, float3 albedo)
{
// イラディアンスとディレクショナル テクスチャをサンプリングします。
float3 irradiance = tex2D(g_IrradianceSampler, uv).xyz;
float3 direction = tex2D(g_DirectionalSampler, uv).xyz * 2.0f - 1.0f;
float3 radiosityNormal = tex2D(g_RadiosityNormalSampler, uv).xyz * 2.0f - 1.0f;
float cosTheta = dot(radiosityNormal, normal);
float H = 0.5f - cosTheta * 0.5f;
return irradiance * max(0, H * albedo + (1 - H) * DirectionalInfluence(direction, radiosityNormal, normal));
}
別の方法として、各ピクセルの入射光の主要な方向は、カラー チャネルごとに計算することができます。これは、色分けされたディレクショナル イラディアンスと呼ばれ、カラー コンポーネントごとに 1 つずつ、3 つのディレクショナル テクスチャが得られます。これらのテクスチャを使用してディレクショナル イラディアンスを計算するためのシェーダー コードは、前述の例のものと似ています。
float3 SeparatedDirectionalIrradiance(float2 uv, float3 normal, float3 albedo)
{
// イラディアンスとディレクショナル テクスチャをサンプリングします。
// イラディアンス テクスチャの w コンポーネントは無視できます。
float3 irradiance = tex2D(g_IrradianceSampler, uv).xyz;
float4 radiosityNormal = tex2D(g_RadiosityNormalSampler, uv).xyz * 2.0f - 1.0f;
float3 directionR = tex2D(g_DirectionalSampler, uv).xyz * 2.0f - 1.0f;
float3 directionG = tex2D(g_DirectionalSamplerG, uv).xyz * 2.0f - 1.0f;
float3 directionB = tex2D(g_DirectionalSamplerB, uv).xyz * 2.0f - 1.0f;
float cosTheta = dot(radiosityNormal, normal);
float H = 0.5f - cosTheta * 0.5f;
float3 directionalInfluence = float3(
DirectionalInfluence(directionR, radiosityNormal, normal),
DirectionalInfluence(directionG, radiosityNormal, normal),
DirectionalInfluence(directionB, radiosityNormal, normal)
);
return irradiance * max(float3(0, 0, 0), H * albedo + (1 - H) * directionalInfluence);
}
これらの例では、ディレクショナル テクスチャには符号なしバイト形式が使用され、値はシェーダーで [0,1] から [-1,1] の範囲にスケーリングされます。これは SNORM テクスチャ形式と同等です。
間接スペキュラー
ライティング環境の主要な方向を前提として、スペキュラー ハイライトを試行錯誤して作成することができます。このための方法は多数ありますが、このページではフレームワーク例で使用されている手法を示しています。特に、ライティング環境について追加情報を取り入れることができる場合、アプリケーションによっては別の式の方がうまく機能する場合があります。
ここに示すアプローチは、低いスペキュラー パワーで機能を発揮します。さらに鮮明に反射させるには、ライティング環境に関する他の情報がさらに必要です。Enlighten は、キューブマップを介してこれをサポートします。詳細は、低レベル キューブマップ APIのページをご覧ください。
float3 CalcDISpecular(float2 uv, float3 normal, float3 reflectVec, float specular_power, float3 irradiance)
{
// [-1,1] にリスケーリングされた、最大強度の方向を取得します。これは、
// ディレクショナル イラディアンスの方向のデコードと同じです。
// リバランスする w コンポーネントは必要ありません。
float3 principalDir;
if (g_UseColourSeparatedDirectionalIrradiance)
{
// 各カラー チャネルの方向を平均します
principalDir = ((tex2D(g_DirectionalSamplerR, uv) +
tex2D(g_DirectionalSamplerG, uv) +
tex2D(g_DirectionalSamplerB, uv)).xyz / 3.0f * 2.0f - 1.0f);
}
else
{
principalDir = tex2D(g_DirectionalSampler, uv).xyz * 2.0f - 1.0f;
}
// コンポーネントを法線方向に圧縮します。
// 方向が水平以下である場合もあります。この
// 修正により、不必要なバックライティングを確実になくします。
principalDir += normal * max(0.0f, -dot(principalDir, normal));
// length からライト分布係数を抽出します。
float fac = length(principalDir);
// 主要な方向の典型的な phong ハイライトを計算します。
// ライト分布係数を使用して、強さと
// スケールの両方を調整します。これは単に試行錯誤に基づくもので、他の式でも
// 代用できます。
float rDotL = max(dot(reflectVec, normalize(principalDir)), 0.0f);
float specular = pow(rDotL, specular_power * fac) * (fac * fac);
// 最終的な結果は、イラディアンスの結果が
// スペキュラー項で調整されたものです。
return specular * irradiance;
}