수업
✅ 주제
- 본 강의는 Specular Light(반사광)을 Phong 반사 모델에 기반하여 구현하는 것이다.
- 빛이 표면에 반사된 방향과 카메라(눈)가 바라보는 방향 사이의 각도에 따라 강한 하이라이트 효과를 계산한다.
- 이전에 구현한 Diffuse(분산광)는 법선 벡터와 빛의 방향에 집중했지만, Specular는 빛이 눈에 어떻게 반사되어 들어오느냐가 핵심이다.
📘 개념
🔹 핵심 비교
| 조명 종류 | 기준 벡터 | 밝기 조건 | 주요 공식 |
|---|
| Diffuse | Normal vs Light | θ 작을수록 밝음 (표면과 빛이 일직선일수록) | dot(N, L) |
| Specular | Reflect vs Eye | θ 작을수록 밝음 (반사된 빛이 눈 방향과 일치할수록) | dot(R, E) |
- Specular 조명은 눈뽕, 즉 금속이나 광택 있는 물체에서 빛이 한 방향으로 강하게 반사되는 현상을 표현한다.
- 하이라이트의 강도는 반사벡터(R)와 시선벡터(E) 간의 각도에 의해 결정되며,
각도가 작을수록 밝기가 커진다.
📐 수학 공식 분석
1. 반사 벡터 R 계산 (Phong 모델)
R = L - 2 * N * dot(L, N)
- L: 광원 방향 벡터
- N: 표면 노멀
- R: 반사 방향 (빛이 튕겨나가는 방향)
2. 시선 벡터 E 계산
E = normalize(CameraPos - WorldPos)
- 카메라 위치에서 픽셀의 월드 위치를 뺀 방향 벡터
3. 최종 밝기 계산
value = saturate(dot(R, E))
specular = pow(value, shininess)
saturate(): 0~1로 클램핑
pow(): shininess 계수로 강조 범위 조절. 클수록 좁고 강함
🧾 용어 정리
| 용어 | 의미 |
|---|
| Specular Light | 반사광. 광택, 유리, 금속 반사 효과 |
| Phong 모델 | 반사광을 계산하는 모델 (R과 E 내적 사용) |
| LightDir | 광원 방향 벡터 |
| LightSpecular | 광원의 반사광 색상 |
| MaterialSpecular | 재질이 반사광을 얼마나 받아들이는가 |
| R (Reflect) | 반사 벡터 |
| E (Eye) | 시선 벡터 (카메라 → 픽셀) |
| pow() | 강조 범위 조절. 클수록 하이라이트 영역이 작고 강함 |
| View 행렬 | 카메라 공간으로 변환하는 행렬. 위치 정보 포함 |
🧠 코드 분석
📁 HLSL 셰이더: Lighting_Specular.fx
1. 변수 정의
float3 LightDir;
float4 LightSpecular;
float4 MaterialSpecular;
Texture2D DiffuseMap;
LightDir: 빛의 방향
LightSpecular: 광원의 색상
MaterialSpecular: 물체의 반사 재질
DiffuseMap: 필요 시 사용 가능
2. 전역 구조체 정의 (00. Global.fx)
struct MeshOutput
{
float4 position : SV_POSITION;
float3 worldPosition : POSITION1;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
};
- 추가된
worldPosition은 카메라 위치와의 방향 계산에 필요
3. 정점 셰이더(VS)
MeshOutput VS(VertexTextureNormal input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position; // World 좌표 보관
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
return output;
}
4. 픽셀 셰이더(PS)
float4 PS(MeshOutput input) : SV_TARGET
{
float3 R = LightDir - (2 * input.normal * dot(LightDir, input.normal)); // 반사 벡터
R = normalize(R);
float3 cameraPosition = -V._41_42_43; // View 행렬로부터 카메라 위치 추출
float3 E = normalize(cameraPosition - input.worldPosition); // 시선 벡터
float value = saturate(dot(R, E)); // R과 E 사이 내적
float specular = pow(value, 10); // 강조 조절
float4 color = LightSpecular * MaterialSpecular * specular;
return color;
}
dot(R, E) 내적 결과가 클수록 → 시선 방향과 반사 방향이 유사함 → 밝음
pow() 값이 클수록 → 더 작고 선명한 하이라이트
📁 C++: SpecularDemo.cpp
1. Init()
_shader = make_shared<Shader>(L"11. Lighting_Specular.fx");
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -10.f });
_obj = make_shared<GameObject>();
_obj->GetMeshRenderer()->SetShader(_shader);
_obj->GetMeshRenderer()->SetMesh(...);
_obj->GetMeshRenderer()->SetTexture(...);
_obj2 = make_shared<GameObject>();
_obj2->GetOrAddTransform()->SetPosition(Vec3{ 0.5f, 0.f, 2.f });
_obj2->GetMeshRenderer()->SetShader(_shader);
_obj2->GetMeshRenderer()->SetMesh(...);
_obj2->GetMeshRenderer()->SetTexture(...);
2. Update()
Vec4 light{ 1.f };
_shader->GetVector("LightSpecular")->SetFloatVector((float*)&light);
Vec3 lightDir{ 1.f, -1.f, 0.f };
lightDir.Normalize();
_shader->GetVector("LightDir")->SetFloatVector((float*)&lightDir);
Vec4 material(1.f);
_shader->GetVector("MaterialSpecular")->SetFloatVector((float*)&material);
_obj->Update();
_obj2->Update();
- 오른쪽 아래 방향으로 빛 설정
- 흰색 빛으로 모든 색 수용
🔬 실험과 결과
| 테스트 | 결과 |
|---|
lightDir{1, 0, 0} | 오른쪽에서 → 왼쪽 면 밝게 |
lightDir{1, -1, 0} | 오른쪽 아래 → 왼쪽 위 밝게 |
pow(value, 1) | 하이라이트 범위 넓음, 부드러움 |
pow(value, 10) | 작고 강한 하이라이트 (눈뽕 효과) |
✅ 핵심
- Specular 조명은 카메라를 기준으로 반사광의 강도를 계산하는 조명 모델이다.
- Phong 반사 모델에 기반하여, 반사벡터 R과 시선벡터 E의 내적을 통해 밝기를 구하고,
pow() 함수로 강조 범위를 조절한다.
- saturate()로 범위 제한, 카메라 위치는 View 행렬로부터 계산
- Specular 조명은 시선 중심의 반사광 계산이며,
Diffuse와 Ambient와 함께 조합하면 가장 기본적인 조명 세트를 구성할 수 있다.