본문으로 바로가기

Lighting 용어

category Technical Report/Tutorials 2025. 4. 29. 14:04
반응형

2012년 포스팅 수정/보완

 

Vertex lighting : 조명을 버텍스(Vertex) 단위에서 계산하여, 각 버텍스에 조명 색상을 저장한 후 이를 삼각형 내부로 보간(Interpolation)하는 방식. 이로 인해 계산량이 적어 퍼포먼스가 뛰어나지만, 조명 변화가 세밀하게 표현되지 않고 넓게 퍼지거나 각져 보일 수 있다. PS3의 경우 버텍스 연산량이 픽셀 연산량에 비해 여유가 있다보니 이를 버텍스 라이팅에 활용되는 사례가 많다. 현재는 거의 사용되지 않는 방식

Shader에서 vertex color만 지원한다면 간단하게 조명효과 구현이 가능한 방법. 디테일한 연출을 할려면 mesh가 증가하겠지만 모델링단계에서 이런부분을 고민한다면 어느정도 연출은 가능하다.

Per pixel lighting : 각 픽셀(프래그먼트) 단위에서 조명 연산을 수행하는 방식. 실시간 조명에서는 픽셀마다 실시간 조명 계산을 하고 처리한다. 대부분의 라이팅 계산은 이 방식을 사용한다.

 

Shader stage에서 vertex 스테이지에서 처리하느냐, pixel stage에서 처리하느냐로 구분하는 방법이다. Lighting과 관련한 용어는 PBR에서 각 단계별로 라이팅을 처리하는 순서를 기준으로 살펴보면 아래와 같다.

  1. Direct Diffuse : 광원이 표면에 직접 닿아 퍼지는 부드러운 확산광
  2. Direct Specular : 광원이 표면에 직접 닿아 생기는 반짝이는 광택 (거울 반사)
  3. Indirect Diffuse : 주변 물체에 반사된 간접 빛이 부드럽게 퍼지는 확산광
  4. Indirect Specular : 주변 환경이 표면에 반사되어 보이는 거울 반사 (환경맵 Reflection 등)

 

 

1. Diffuse Term

  • Lambert : 가장 기본적인 확산 반사 모델. 부드러운 확산, 거칠기 고려 없음.
  • Oren-Nayar : 거칠기(Roughness) 있는 표면에서 확산 반사를 좀 더 자연스럽게 표현.
  • Disney Diffuse : Oren-Nayar를 현실적으로 단순화한 현대 실시간 렌더링용 Diffuse 모델.[각주:1]

 

code example

Lambert


// Inputs:
// normalWS : 월드 스페이스 노멀 벡터 (normalized)
// lightDirWS : 광원 방향 벡터 (normalized, 광원 → 표면 방향)

float LambertDiffuse(float3 normalWS, float3 lightDirWS)
{
    return saturate(dot(normalWS, lightDirWS));
}

 

Oren-Nayar diffuse


// Inputs:

// normalWS : 월드 스페이스 노멀 벡터
// lightDirWS : 광원 방향 벡터
// viewDirWS : 카메라 방향 벡터
// roughness : 표면 거칠기 (0 = 매끄러움, 1 = 매우 거칠음)

float OrenNayarDiffuse(float3 normalWS, float3 lightDirWS, float3 viewDirWS, float roughness)
{
    float ndotl = saturate(dot(normalWS, lightDirWS));
    float ndotv = saturate(dot(normalWS, viewDirWS));
    float3 v_perp_n = normalize(viewDirWS - normalWS * ndotv);
    float3 l_perp_n = normalize(lightDirWS - normalWS * ndotl);

    float cos_phi_diff = saturate(dot(v_perp_n, l_perp_n));

    float rough2 = roughness * roughness;
    float A = 1.0 - 0.5 * (rough2 / (rough2 + 0.33));
    float B = 0.45 * (rough2 / (rough2 + 0.09));

    float sin_alpha = (ndotl < ndotv) ? sqrt(1.0 - ndotl * ndotl) : sqrt(1.0 - ndotv * ndotv);
    float tan_beta = (ndotl < ndotv) ? sqrt(1.0 - ndotl * ndotl) / ndotl : sqrt(1.0 - ndotv * ndotv) / ndotv;

    float orenNayar = ndotl * (A + B * cos_phi_diff * sin_alpha * tan_beta);
    return saturate(orenNayar);
}

 

Disney Diffuse


// Inputs:
// normalWS: 월드 스페이스 노멀 벡터
// lightDirWS: 광원 방향 벡터
// viewDirWS: 카메라 방향 벡터
// roughness: 표면 거칠기 (0 = 매끄러움, 1 = 거칠음)

float DisneyDiffuse(float3 normalWS, float3 lightDirWS, float3 viewDirWS, float roughness)
{
    float ndotl = saturate(dot(normalWS, lightDirWS));
    float ndotv = saturate(dot(normalWS, viewDirWS));
    float3 halfVector = normalize(lightDirWS + viewDirWS);

    float ldotH = saturate(dot(lightDirWS, halfVector));

    float energyBias = 0.5 * roughness;
    float energyFactor = 1.0 / (roughness * 0.5 + 1.0);

    float fd90 = energyBias + 2.0 * ldotH * ldotH * roughness;
    float lightScatter = 1.0 + (fd90 - 1.0) * pow(1.0 - ndotl, 5.0);
    float viewScatter  = 1.0 + (fd90 - 1.0) * pow(1.0 - ndotv, 5.0);

    return ndotl * lightScatter * viewScatter * energyFactor;
}

 

 

2. Specular Term

  • Phong : 고전적인 광택 모델. 간단하지만 물리적으로 부정확.
  • Blinn-Phong : Phong 개선판. 반각(Half Vector) 기반으로 계산 효율 증가.
  • Cook-Torrance : 마이크로페이셜 모델. 현실적인 광택과 에너지 보존 적용.
  • GGX (Trowbridge-Reitz) : Cook-Torrance 기반 현대 표준. 거칠기가 높은 표면에서도 자연스러운 긴 하이라이트 표현 가능.
  • Anisotropic GGX : 헤어나 털(fur), 브러쉬드 메탈 같은 비등방성 표현을 위해 사용하는 모델. [각주:2]

code example

Phong


float PhongSpecular(float3 normal, float3 lightDir, float3 viewDir, float shininess)
{
    float3 reflectDir = reflect(-lightDir, normal);
    return pow(saturate(dot(viewDir, reflectDir)), shininess);
}

 

Blinn Phong


float BlinnPhongSpecular(float3 normal, float3 lightDir, float3 viewDir, float shininess)
{
    float3 halfVector = normalize(lightDir + viewDir);
    return pow(saturate(dot(normal, halfVector)), shininess);
}

 

Cook-Torrance


float3 FresnelSchlick(float cosTheta, float3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

float CookTorranceSpecular(float3 normal, float3 lightDir, float3 viewDir, float roughness, float3 F0)
{
    float3 halfVector = normalize(lightDir + viewDir);
    float NdotL = saturate(dot(normal, lightDir));
    float NdotV = saturate(dot(normal, viewDir));
    float NdotH = saturate(dot(normal, halfVector));
    float VdotH = saturate(dot(viewDir, halfVector));

    float alpha = roughness * roughness;

    // D: Normal Distribution Function (Beckmann or GGX)
    float D = alpha * alpha / (PI * pow((NdotH * NdotH) * (alpha * alpha - 1.0) + 1.0, 2));

    // G: Geometry Function (Smith approximation)
    float G = min(1.0, min((2.0 * NdotH * NdotV) / VdotH, (2.0 * NdotH * NdotL) / VdotH));

    // F: Fresnel
    float3 F = FresnelSchlick(VdotH, F0);

    return (D * G * F) / max(4.0 * NdotL * NdotV, 0.001);
}

 

GGX


float GGX_Distribution(float NdotH, float alpha)
{
    float a2 = alpha * alpha;
    float denom = (NdotH * NdotH) * (a2 - 1.0) + 1.0;
    return a2 / (PI * denom * denom);
}

float GGX_Geometry(float NdotL, float NdotV, float alpha)
{
    float k = (alpha + 1.0) * (alpha + 1.0) / 8.0;
    float G1L = NdotL / (NdotL * (1.0 - k) + k);
    float G1V = NdotV / (NdotV * (1.0 - k) + k);
    return G1L * G1V;
}

float3 GGXSpecular(float3 normal, float3 lightDir, float3 viewDir, float roughness, float3 F0)
{
    float3 halfVector = normalize(lightDir + viewDir);
    float NdotL = saturate(dot(normal, lightDir));
    float NdotV = saturate(dot(normal, viewDir));
    float NdotH = saturate(dot(normal, halfVector));
    float VdotH = saturate(dot(viewDir, halfVector));

    float alpha = roughness * roughness;
    float D = GGX_Distribution(NdotH, alpha);
    float G = GGX_Geometry(NdotL, NdotV, alpha);
    float3 F = FresnelSchlick(VdotH, F0);

    return (D * G * F) / max(4.0 * NdotL * NdotV, 0.001);
}

 

 

3. Indirect Diffuse Term

  • Lightmap : 미리 계산해서 텍스처로 저장. 실시간 퍼포먼스 최적화용.
  • Irradiance Map : 환경맵을 낮은 해상도로 압축해 Diffuse 반사처럼 처리.
  • Screen Space GI (SSGI) : 화면에 보이는 정보만을 기반으로 간접광을 실시간 근사.
  • Voxel Cone Tracing : 장면을 Voxel로 변환 후 Cone tracing으로 GI 효과 근사.

Ambient Light (Skybox, Color, Gradient) / Light Probe (Dynamic Object용) / Baked Lightmap Indirect (Static Object용)등이 indirect diffuse term에 반영된다. Indirect Diffuse는 에너지 보존법칙(Energy Conservation)을 따르며, 표면 거칠기나 재질 특성에 따라 간접광 반사량이 적절히 조정된다.(※ 예를 들어 rough한 표면은 광이 더 확산되고 specular는 줄어드는 식)

 

4. Indirect Specular Term

  • Reflection Probe : 특정 위치에 구형 프로브를 배치하고 환경을 캡처하여 반사 처리.
  • Screen Space Reflection (SSR) : 현재 화면 버퍼를 샘플링하여 반사를 근사.
  • Planar Reflection : 평면(거울) 표면을 위한 정확한 리플렉션 (ex: 물 표면)
  • Raytraced Reflection : RTX 기반 하드웨어 레이 트레이싱으로 실제 반사를 계산.

반사 벡터(Reflection Vector)를 기준으로 Reflection Probe 또는 Skybox를 샘플링 / Roughness(거칠기)에 따라 Reflection이 퍼짐 (Mipmap 레벨 조정) / Microfacet 기반 반사 이론(GGX Distribution)등이 여기에 반영된다.

 

  1. Unity, Unreal의 PBR은 이걸 사용 [본문으로]
  2. Fresnel term에서 Shifted Fresnel 또는 Dual Specular Lobes (2개의 광택) 모델을 적용해서 더욱 사실적인 하이라이트 표현을 가지게 만든다 [본문으로]
반응형