수업
주제
- DirectX11 기반 C++ 엔진에서 정점의 Normal 벡터를 정의하고, 이를 바탕으로 조명 연산을 수행하는 방법을 학습한다.
- 표면에 수직인 Normal 벡터와 광원 방향 벡터의 내적(dot product) 결과를 통해, 픽셀 셰이더 단계에서 광량을 조절하는 기본 조명 기법을 구현한다.
- 정점 데이터에 Normal을 포함한 구조체를 생성하고, Quad, Cube, Sphere 등의 도형에 적절한 Normal을 할당하여 기초적인 Diffuse 조명 효과를 실현한다.
개념
- Normal Vector(법선 벡터): 표면에 수직인 단위 벡터. 조명 연산의 방향성을 판단하는 기준이 된다.
- Dot Product(내적): 두 벡터의 각도를 코사인 값으로 반환.  
- dot(normal, -light)→ 0도: 1(밝음), 90도: 0(어두움)
 
- 정점 기반 조명(Vertex Lighting): 각 정점에 Normal을 정의하고 이를 기반으로 조명을 계산하는 방식.
- Transform Normal: 위치 변환(translation)을 제외하고 회전만 적용된 Normal 벡터 변환.
- float3x3 World 행렬: World matrix에서 회전 행렬만 추출하여 Normal에 곱하는 구조.
용어정리
| 용어 | 설명 | 
|---|
| Normal Vector | 표면에 수직인 벡터. 광원과의 각도를 통해 픽셀 밝기를 계산 | 
| dot(N, -L) | Normal과 광원 방향(-L)의 내적. 1~0 범위의 조명 세기 결과 | 
| VertexTextureNormalData | 위치, UV, Normal 정보를 갖는 사용자 정의 정점 구조체 | 
| Transform Normal | Normal 벡터에 평행이동을 제외한 회전 변환만 적용 | 
| float3x3 World | 월드 행렬의 회전 행렬(3x3)만 추출한 형태 | 
| LightDir | 월드 기준의 광원 방향 벡터 | 
| Wireframe | 메시 외곽선만 보이는 모드. 구조 확인용 | 
코드 분석
✅ 1. 정점 구조체 정의 - VertexTextureNormalData
struct VertexTextureNormalData
{
    Vec3 position = { 0, 0, 0 };
    Vec2 uv = { 0, 0 };
    Vec3 normal = { 0, 0, 0 };
};
- 기존 정점 정보(position, uv)에 Normal을 추가.
- 이후 조명 계산을 위해 반드시 필요한 구성이다.
✅ 2. GeometryHelper 도형별 Normal 정의
▷ CreateQuad
vtx[i].normal = Vec3(0.f, 0.f, -1.f);
- Quad는 평면이므로 모든 정점의 Normal이 동일하다.
- Look 방향의 반대(-Z)가 평면의 Normal이 된다.
▷ CreateCube
Vec3(0.0f, 0.0f, -1.0f);
Vec3(0.0f, 0.0f, 1.0f);
Vec3(0.0f, 1.0f, 0.0f);
Vec3(0.0f, -1.0f, 0.0f);
Vec3(±1.0f, 0.0f, 0.0f);
- Cube는 각 면의 방향에 따라 정해진 Normal을 정점에 설정한다.
▷ CreateSphere
v.normal = v.position;
v.normal.Normalize();
- Sphere는 원점에서 정점 위치로 향하는 벡터가 바로 Normal.
- 정점의 위치 벡터를 정규화하여 Normal로 사용한다.
✅ 3. Shader 구현 – 07. Normal.fx
▷ Vertex Shader (VS)
output.normal = mul(input.normal, (float3x3)World);
- Normal에 회전만 적용.
- 4x4 행렬이 아닌 3x3 행렬을 사용하여 위치 이동은 배제하고 회전만 적용한다.
▷ Pixel Shader (PS)
float3 normal = normalize(input.normal);
float3 light = -LightDir;
return Texture0.Sample(Sampler0, input.uv) * dot(light, normal);
- Normal과 LightDir의 내적을 통해 조명 세기를 계산.
- 텍스처 색상에 dot 값을 곱하여 밝기를 조절.
▷ 와이어프레임 상태 추가
RasterizerState FillModeWireFrame
{
    FillMode = Wireframe;
};
- 도형 구조를 확인할 수 있도록 Wireframe 패스를 추가.
✅ 4. NormalDemo 클래스 구현
_shader = make_shared<Shader>(L"07. Normal.fx");
_geometry = make_shared<Geometry<VertexTextureNormalData>>();
GeometryHelper::CreateCube(_geometry); 
_texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
_lightDir = Vec3(-1.f, 0.f, 0.f);
- Shader는 Normal 연산용 fx 파일로 설정.
- 도형은 Normal 정보 포함 구조로 생성.
- 빛은 오른쪽에서 왼쪽 방향으로 설정됨.
핵심
- Normal 벡터는 조명 연산의 핵심 요소로, 정점 단위로 정의되어야 한다.
- Normal과 조명 방향 벡터의 내적(dot) 결과를 기반으로 픽셀 밝기를 결정한다.
- Transform Normal 시 평행이동은 제외하고 회전만 적용되어야 하므로 float3x3을 사용한다.
- 픽셀 셰이더에서는 텍스처 색상에 dot 결과를 곱해 밝기 효과를 표현한다.
- Sphere에서는 정점 위치 벡터 자체가 Normal 벡터가 되며, Cube는 각 면 기준으로 Normal을 고정한다.
- Wireframe 모드를 통해 메시 구조와 조명 효과를 시각적으로 디버깅할 수 있다.
- 이 구조는 향후 NormalMap 기반 조명, Specular, Phong/Blinn-Phong 모델로 확장 가능하다.