2012년 포스팅 수정/보완
Vertex lighting : 조명을 버텍스(Vertex) 단위에서 계산하여, 각 버텍스에 조명 색상을 저장한 후 이를 삼각형 내부로 보간(Interpolation)하는 방식. 이로 인해 계산량이 적어 퍼포먼스가 뛰어나지만, 조명 변화가 세밀하게 표현되지 않고 넓게 퍼지거나 각져 보일 수 있다. PS3의 경우 버텍스 연산량이 픽셀 연산량에 비해 여유가 있다보니 이를 버텍스 라이팅에 활용되는 사례가 많다. 현재는 거의 사용되지 않는 방식
Per pixel lighting : 각 픽셀(프래그먼트) 단위에서 조명 연산을 수행하는 방식. 실시간 조명에서는 픽셀마다 실시간 조명 계산을 하고 처리한다. 대부분의 라이팅 계산은 이 방식을 사용한다.
Shader stage에서 vertex 스테이지에서 처리하느냐, pixel stage에서 처리하느냐로 구분하는 방법이다. Lighting과 관련한 용어는 PBR에서 각 단계별로 라이팅을 처리하는 순서를 기준으로 살펴보면 아래와 같다.
- Direct Diffuse : 광원이 표면에 직접 닿아 퍼지는 부드러운 확산광
- Direct Specular : 광원이 표면에 직접 닿아 생기는 반짝이는 광택 (거울 반사)
- Indirect Diffuse : 주변 물체에 반사된 간접 빛이 부드럽게 퍼지는 확산광
- 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)등이 여기에 반영된다.
'Technical Report > Tutorials' 카테고리의 다른 글
| display/print size comparison (0) | 2022.07.29 |
|---|---|
| Universal Render Pipeline에서 ‘2D Render Pipeline Project Setting’ (0) | 2021.10.03 |
| HDRP for Artist (0) | 2020.06.04 |
| 필드에 빛을 입히다 - Unity Progressive Lightmapper (2) | 2019.10.17 |
| Unity Light Probes LightingData.asset (0) | 2018.08.21 |