Image Based Lighting
Diffuse Irradiance
- 직접적인 분석 조명이 아닌 주변 환경을 하나의 큰 광원으로 간주해 조명하는 방식이다.
- 일반적으로 조명 방정식에서 직접 사용할 수 있는 환경맵을 조작해 조명 방정식에서 직접 사용할 수 있도록 수행된다.
- 환경의 조명을 고려할 때 객체가 훨씬 물리적으로 정확해 보이기 때문에 PBR에 IBL이 유용하다.
- 주변 환경으로부터 들어오는 빛의 방향에 따라 약간의 광도가 생겨 요구사항이 생긴다.
1) 임의의 방향 벡터가 주어진 경우 장면의 밝기를 검색 할 수 있는 방법이 필요하다. 이를 위해 환경이나 장면의 방사 조도를
표현하는 방법은 큐브맵 형태이다. 이러한 큐브맵을 감안할 때 큐브 맵의 모든 텍셀을 하나의 단일 광원으로 시각화하여 임의의
방향 벡터로 샘플링 함으로써 그 방향에서 장면의 광도를 검색한다.
2) 통합을 해결하는 것은 빠르고 실시간이어야 한다. 이를 위해 모든 샘플 방향에 대해 적분 결과를 저장하는 사전 계산된 큐브 맵을
만들어 두는데, 이 큐브 맵은 복잡한 큐브 맵이 효과적으로 어떤 방향에서든 장면의 방사량을 직접 샘플링 할 수 있도록 보는
조사 맵을 나타낸다.

float4 IBL_Convolution_PS(PixelIn pin) : SV_TARGET
{
float3 normal = normalize(pin.PosL);
float3 irradiance = float3(0.0f, 0.0f, 0.0f);
float3 up = float3(0.0f, 1.0f, 0.0f);
float3 right = cross(up, normal);
up = cross(normal, right);
float sampleDelta = 0.025f;
float nrSamples = 0.0f;
for (float phi = 0.0f; phi < 2.0 * PI; phi += sampleDelta)
{
for (float theta = 0.0f; theta < 0.5 * PI; theta += sampleDelta)
{
float3 tangentSample = float3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
float3 sampleVec = (tangentSample.x * right) + (tangentSample.y * up) + (tangentSample.z * normal);
irradiance += gSkyCube.Sample(gSamWrapLinear, sampleVec).rgb * cos(theta) * sin(theta);
nrSamples++;
}
}
irradiance = PI * irradiance * (1 / nrSamples);
return float4(irradiance, 1.0f);
}
- 조사 맵 ( Irradiance Map )을 구하는 Shader Code.
Specular Image Based Lighting
- 모든 가능한 관찰자의 방향을 포함해 모든 들어오는 빛의 방향에 대한 적분을 풀려고 하는것은 실시간으로 계산하기에는 너무
비싸다. 그래서 Epic Games에서 고안한 방법으로 Specular 적분을 두 개의 개별 적분으로 나누어 사전 필터링 환경 맵으로
만들어 두는 것이다.
- 거칠기를 고려해야 하기에 Convolute 각 조도 수준에 대해, 사전 필터링된 환경 맵 밉맵 수준에서 순차적으로 Blur 결과를
저장한다.

float4 IBL_PrefilterMap_PS(PixelIn pin) : SV_TARGET
{
float3 normalVec = normalize(pin.PosL);
float3 R = normalVec;
float3 viewDir = R;
float roughness2 = gRoughness * gRoughness;
float3 PrefilteredColor = float3(0.0f, 0.0f, 0.0f);
float totalWeight = 0.0f;
const uint NumSamples = 1024;
for (uint i = 0; i < NumSamples; i++)
{
float2 Xi = Hammersley(i, NumSamples);
float3 halfwayVec = ImportanceSampleGGX(Xi, roughness2, normalVec);
float3 lightDir = 2.0f * dot(viewDir, halfwayVec) * halfwayVec - viewDir;
float NdotL = saturate(dot(normalVec, lightDir));
if (NdotL > 0)
{
float D = NormalDistributionGGXTR(normalVec, halfwayVec, roughness2);
float NdotH = max(dot(normalVec, halfwayVec), 0.0f);
float HdotV = max(dot(halfwayVec, viewDir), 0.0f);
float pdf = D * NdotH / (4.0f * HdotV) + 0.0001f;
float resolution = 512.0f;
float saTexel = 4.0f * PI / (6.0f * resolution * resolution);
float saSample = 1.0f / (float(NumSamples) * pdf + 0.0001f);
float mipLevel = gRoughness == 0.0 ? 0.0 : 0.5 * log2(saSample / saTexel);
PrefilteredColor += gSkyCube.SampleLevel(gSamWrapLinear, lightDir, 0).rgb * NdotL;
totalWeight += NdotL;
}
}
PrefilteredColor /= totalWeight;
return float4(PrefilteredColor, 1.0f);
}
- 재질에 따른 사전 필터링 환경맵 밉맵 ( Prefilter Map) Shader Code.
BRDF 2D Look Up Texture ( LUT )
- Epic Games는 BRDF 통합 맵이라고 하는 2D Look Up Texture의 다양한 조도 값에 대한 각 정상 및 조명 방향 조합에 대한 사전
계산된 BRDF의 응답을 저장한다. 2D Look Up Texture는 표면의 Fresnel 응답에 스케일과 바이어스 값을 출력해 분할된
Specular 적분의 두 번째 부분을 제공한다.

float2 IBL_IntegrateBRDF_PS(PixelIn pin) : SV_TARGET
{
float NdotV = pin.Tex.y;
float roughness = 1.0f - pin.Tex.x;
float roughness2 = roughness * roughness;
float3 viewDir;
viewDir.x = sqrt(1.0f - (NdotV * NdotV)); // sin
viewDir.y = 0;
viewDir.z = NdotV; // cos
float A = 0.0f;
float B = 0.0f;
float3 normalVec = float3(0.0f, 0.0f, 1.0f);
const uint numSamples = 1024;
for (uint i = 0; i < numSamples; i++)
{
float2 Xi = Hammersley(i, numSamples);
float3 halfwayVec = ImportanceSampleGGX(Xi, roughness2, normalVec);
float3 lightDir = normalize((2.0f * dot(viewDir, halfwayVec) * halfwayVec) - viewDir);
float NdotL = saturate(lightDir.z);
float NdotH = saturate(halfwayVec.z);
float VdotH = saturate(dot(viewDir, halfwayVec));
if (NdotL > 0)
{
float G = IBLGeometrySmith(normalVec, viewDir, lightDir, roughness2);
float G_Vis = (G * VdotH) / (NdotH * NdotV);
float Fc = pow(1.0f - VdotH, 5.0f);
A += (1.0f - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
return float2(A, B) / numSamples;
}
- BRDF 2D Look Up Texture ( LUT )를 구하는 Shader Code.
Image Based Lighting 연산
- 위의 세가지 사전 작업이 끝난 후 반사 벡터를 사용해 필터링된 환경 맵을 샘플링해 표면의 간접 반사를 얻은 후 표면 거칠기를
기준으로 적절한 밉 레벨을 샘플링해 거친 표면에 거울 반사를 흐리게 처리한다.
#define MAX_REF_LOD 4.0
float3 FresnelSchlickRoughness(float NdotV, float3 F0, float roughness)
{
float roughnessPercent = 1.0f - roughness;
return F0 + (max(float3(roughnessPercent, roughnessPercent, roughnessPercent), F0) - F0) * pow(1.0 - NdotV, 5.0f);
}
float3 IBL_EnvironmentLight(in float3 V, in float3 N, in float3 irradiance, in float3 prefilterColor, in float2 brdf, in float3 albedo, in float ao, in float roughness, in float metallic, in float shadow)
{
float3 F0 = lerp(F_ZERO, albedo, metallic);
float3 kS = FresnelSchlickRoughness(max(dot(N, V), 0.0f), F0, roughness);
float3 kD = float3(1.0f, 1.0f, 1.0f) - kS;
kD *= 1.0 - metallic;
float3 diffuse = albedo * irradiance * kD;
float3 specular = prefilterColor * (kS * brdf.x + brdf.y);
return (diffuse + specular) * ao;
}
float4 Light_IBL_PS(PixelIn pin) : SV_TARGET
{
...
float3 irradiance = gIBLIrradiance.Sample(gSamWrapLinear, normal).rgb;
float3 prefilteredColor = gIBLPrefilter.SampleLevel(gSamWrapLinear, reflect(-ViewDirection, normal), roughness * MAX_REF_LOD).rgb;
float2 brdf = gBRDFlut.Sample(gSamWrapLinear, float2(max(dot(normal, ViewDirection), 0.0f), roughness)).rg;
litColor += IBL_EnvironmentLight(ViewDirection, normal, irradiance, prefilteredColor, brdf,
albedo, ao, roughness, metallic, shadows);
...
return float4(litColor, 1.0f);
}
- 사전 작업이 끝난 환경맵을 기준으로 Image Based Lighting Shader Code.
적용 영상
'DirectX 11' 카테고리의 다른 글
[DirectX 11] Lambert / Half Lambert (0) | 2022.02.07 |
---|---|
[DirectX 11] Tone Mapping (0) | 2022.02.03 |
[DirectX 11] PBR (Physically Based Rendering) (0) | 2022.01.21 |
[DirectX 11] Deferred Rendering FXAA (0) | 2022.01.16 |
[DirectX 11] Order Independent Transparency (0) | 2022.01.07 |