라이팅 개념
Light
게임에서 빛의 종류
-
- Directional Light (방향광)
- ‘태양빛’ 같이 ‘방향’만 있으며
거리에 따른 ‘감쇠’가 없음
(보통 LightDir 로 처리)
- Directional Light (방향광)
-
- Point Light(점광원)
- ‘전구’처럼 한 점에서 모든 방향으로 퍼짐
거리 감쇠(Attenuation) 구현 필요
(L = normalize(LightPos - FragPos));
- Point Light(점광원)
-
- Spot Light(스포트라이트)
- 원뿔 모양으로 퍼지는 빛(손전등)
각도와 거리 감쇠를 함께 계산
- Spot Light(스포트라이트)
-
- Ambient Light(환경광)
- 전체적으로 약간 밟게 유지하는 균일한 빛
직접광이 없어도 완전히 까맣지 않게
(Phong 모델에선 상수 값으로 덮어씌워 구현)
(PBR 등에선 시뮬레이션 등을 통해 실제로 환경광을 계산)
- Ambient Light(환경광)
광원 모델
-
- Lambert (램버트 조명)
- Diffuse를 구할때 많이 사용
I=kd⋅(N⋅L)⋅IL
- I : 최종 밝기
- Kd : Diffuse의 반사율 (재질 고유 색상, Albedo)
- N : 법선 벡터(Normal)
- L : 광원 방향 벡터(Light Direction, 정규화됨)
- (N⋅L) : 내적을 통한 입사각 효과 (빛이 직각일수록 많이 받으며, 측면일수록 약해짐)
- IL : 광원 자체의 세기(Intensity of Light Source)
- Lambert (램버트 조명)
-
- Phong Reflection Model
- 고전적인 조명 공식
I=kaIa+kd(N⋅L)IL+ks(R⋅V)αIL
- I : 최종 밝기
- Ka : Ambient 반사율 (재질이 환경광을 반사하는 정도)
- Ia : Ambient Light 강도(환경광 세기)
- Kd : Diffuse의 반사율 (재질 고유 색상, Albedo)
- N : 법선 벡터(Normal)
- L : 광원 방향 벡터(Light Direction, 정규화됨)
- (N⋅L) : 내적을 통한 입사각 효과 (빛이 직각일수록 많이 받으며, 측면일수록 약해짐)
- IL : 광원 자체의 세기(Intensity of Light Source)
- Ks : Specular 반사율 (얼마나 빛을 잘 반사하는지, 0이면 없음)
- R : 반사 벡터(빛이 표면에서 반사된 방향)
- V : 뷰어(카메라) 방향 벡터
- (R⋅V)α : 스페큘러 강도 (α : 하이라이트의 날카로움 -> Shininess)
- Phong Reflection Model
-
- Blinn-Phong
- Phong의 개선 버전
Half Vector를 이용
연산량 절감 + 더 자연스러운 하이라이트
I=kaIa+kd(N⋅L)IL+ks(N⋅H)αIL
- H : HalfVector(광원방향 L과 뷰어 방향 V의 중간 벡터)
- H=L+V / ∣L+V∣
- H=L+V / ∣L+V∣
- 나머지는 Phong과 동일
- R⋅V 대신, N⋅H 를 이용N⋅H
- Blinn-Phong
-
- PBR(Phsically Based Rendering)
- 물리 기반 모델, 현대 게임 그래픽스 표준(Unreal 등)
fr(L,V)= D(h)⋅F(V,h)⋅G(N,V,L) / 4(N⋅V)(N⋅L)
- fr : BRDF (Bidirectional Reflectance Distribution Function)
- D(h) : Normal Distribution Function (NDF, 거칠기 → Roughness)
- F(V,h) : Fresnel Term (시선 각도에 따른 반사율 변화)
- G(N,V,L) : Geometry Term (마이크로 셰도잉, 빛이 미세 표면에서 가려지는 정도)
- h : half vector
- 𝑁⋅𝑉,𝑁⋅𝐿 : 입사/출사 각도의 영향
- PBR(Phsically Based Rendering)
감쇠
거리 감쇠로서
시작점 -> 거리 D까지의 선형보간
- fallOffStart 까지는 1의 가중치(감쇠 x)
- fallOffEnd를 넘어가면 0의 가중치(보이지 않음)
- 거리가 같더라도 fallOffEnd가 더 긴쪽이 더 밟게 보인다
SpotLight 구현은
‘각도 감쇠’또한 필요하다
(내각,외각에 따라 원뿔 가장자리 처리가 달라진다 함)
PBR 제외 예시코드(HLSL)
float CalcAttenuation(float d, float falloffStart, float falloffEnd)
{
// Linear falloff
return saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}
float3 BlinnPhong(float3 lightStrength, float3 lightVec, float3 normal,
float3 toEye, Material mat)
{
float3 halfway = normalize(toEye + lightVec);
float3 specular = mat.specular * pow(max(dot(halfway, normal), 0.0), mat.shininess);
return mat.ambient + (mat.diffuse + specular) * lightStrength;
}
float3 ComputeDirectionalLight(Light L, Material mat, float3 normal,
float3 toEye)
{
float3 lightVec = -L.direction;
float ndotl = max(dot(lightVec, normal), 0.0);
float3 lightStrength = L.strength * ndotl;
return BlinnPhong(lightStrength,lightVec,normal,toEye,mat);
}
float3 ComputePointLight(Light L, Material mat, float3 pos, float3 normal,
float3 toEye)
{
float3 lightVec = L.position - pos;
// 쉐이딩할 지점부터 조명까지의 거리 계산
float d = length(lightVec);
// 너무 멀면 조명이 적용되지 않음
if (d > L.fallOffEnd)
{
return float3(0.0, 0.0, 0.0);
}
else
{
lightVec /= d;
float ndotl = max(dot(lightVec, normal), 0.0);
float3 lightStrength = L.strength * ndotl;
float att = CalcAttenuation(d, L.fallOffStart, L.fallOffEnd);
lightStrength *= att;
return BlinnPhong(lightStrength,lightVec,normal,toEye,mat);
}
}
float3 ComputeSpotLight(Light L, Material mat, float3 pos, float3 normal,
float3 toEye)
{
float3 lightVec = L.position - pos;
// 쉐이딩할 지점부터 조명까지의 거리 계산
float d = length(lightVec);
// 너무 멀면 조명이 적용되지 않음
if (d > L.fallOffEnd)
{
return float3(0.0f, 0.0f, 0.0f);
}
else
{
lightVec /= d;
float ndotl = max(dot(lightVec, normal), 0.0);
float3 lightStrength = L.strength * ndotl;
float att = CalcAttenuation(d, L.fallOffStart, L.fallOffEnd);
lightStrength *= att;
float spotFactor = pow(max(-dot(lightVec, L.direction), 0.0), L.spotPower);
lightStrength *= spotFactor;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
// if에 else가 없을 경우 경고 발생
// warning X4000: use of potentially uninitialized variable
}
hlsli 확장자
일반적으로 HLSL Include의 줄임말
보통 쉐이더 코드에서 공용으로 사용하는 상수,함수, 구조체 등을
모아두는 ‘헤더 파일’ 개념
-
개발자들이 관례적으로 만든 확장자
(C++의 hpp 같이 ‘표준 확장자’는 아님) -
- 사용처?
- 공용 상수 버퍼 정의, 반복 사용 함수, 구조체 정의 등을 포함시킨다
- 사용처?
-
- Item Type(항목 형식)을
- Does not participate in build (빌드에 참여 안함)
으로 설정해야 한다
(아니면 ‘메인 함수’를 찾을 수 없다는 에러 발생)
(그렇지만 쉐이더가 아니므로, main 함수 같은 진입점이 있으면 안됨)
- Item Type(항목 형식)을
예시 파일
// 쉐이더에서 include할 내용들은 .hlsli 파일에 작성
// Properties -> Item Type: Does not participate in build으로 설정
#define MAX_LIGHTS 3 // 쉐이더에서도 #define 사용 가능
#define NUM_DIR_LIGHTS 1
#define NUM_POINT_LIGHTS 1
#define NUM_SPOT_LIGHTS 1
// 재질
struct Material
{
float3 ambient;
float shininess;
float3 diffuse;
float dummy1; // 16 bytes 맞춰주기 위해 추가
float3 specular;
float dummy2;
};
// 조명
struct Light
{
float3 strength;
float fallOffStart;
float3 direction;
float fallOffEnd;
float3 position;
float spotPower;
};
float CalcAttenuation(float d, float falloffStart, float falloffEnd)
{
// Linear falloff
return saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}
...
struct VertexShaderInput
{
float3 posModel : POSITION; //모델 좌표계의 위치 position
float3 normalModel : NORMAL; // 모델 좌표계의 normal
float2 texcoord : TEXCOORD0; // <- 다음 예제에서 사용
// float3 color : COLOR0; <- 불필요 (쉐이딩)
};
struct PixelShaderInput
{
float4 posProj : SV_POSITION; // Screen position
float3 posWorld : POSITION; // World position (조명 계산에 사용)
float3 normalWorld : NORMAL;
float2 texcoord : TEXCOORD;
// float3 color : COLOR; <- 불필요 (쉐이딩)
};
실제 쉐이더 파일에서 사용할때는
#include "Common.hlsli" // 쉐이더에서도 include 사용 가능
이런식으로 사용한다
[unroll] 키워드?
일종의 ‘속성’(Attribute)이며
GPU 쉐이더 컴파일러가 for문을 최적화 할때
컴파일러에게 ‘반복문’을 풀어
하드코딩하라고 조언하는 키워드
‘상수’의 개수를 가지는 루프 문일때
더 효과적(오버헤드 x)
(물론 사용하지 않더라도 컴파일러가 알아서
최적화할수도 있음)
float4 main(PixelShaderInput input) : SV_TARGET
{
float3 toEye = normalize(eyeWorld - input.posWorld);
float3 color = float3(0.0, 0.0, 0.0);
int i = 0;
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-for
// https://forum.unity.com/threads/what-are-unroll-and-loop-when-to-use-them.1283096/
[unroll] // warning X3557: loop only executes for 1 iteration(s), forcing loop to unroll
for (i = 0; i < NUM_DIR_LIGHTS; ++i)
{
color += ComputeDirectionalLight(lights[i], material, input.normalWorld, toEye);
}
// 컴파일러에게 하드 코딩을 하도록 권장
// 반복문을 풀어헤치도록 만들어버림
// 개수가 '상수'라면 효과적
// 이런걸 Attribute라 함
[unroll]
for (i = NUM_DIR_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; ++i)
{
color += ComputePointLight(lights[i], material, input.posWorld, input.normalWorld, toEye);
}
[unroll]
for (i = NUM_DIR_LIGHTS + NUM_POINT_LIGHTS; i < NUM_DIR_LIGHTS + NUM_POINT_LIGHTS + NUM_SPOT_LIGHTS; ++i)
{
color += ComputeSpotLight(lights[i], material, input.posWorld, input.normalWorld, toEye);
}
return useTexture ? float4(color, 1.0) * g_texture0.Sample(g_sampler, input.texcoord) : float4(color, 1.0);
}
댓글남기기