수업


✅ 주제

  • 본 강의는 Diffuse 조명(분산광)을 구현하는 과정이다.
  • 빛의 방향(LightDir)물체 표면의 법선 벡터(Normal) 간의 각도에 따라 밝기 값이 달라지는 원리를 기반으로,
    HLSL 셰이더 코드와 C++ 코드에서 Lambert 조명 모델을 직접 구현한다.

📘 개념

  • Diffuse(디퓨즈, 분산광) 조명은 물체 표면에서 빛이 산란되어 나오는 가장 일반적인 조명 모델이다.
  • 핵심 특징:
    • 각도에 따라 밝기가 달라진다.
    • 빛이 수직으로 들어오면 가장 밝고(0°),
    • 수평으로 들어오면 거의 영향이 없으며(90°),
    • 뒤에서 오면 완전히 어두워진다.
  • 이 원리는 Lambert의 코사인 법칙을 따른다.

🔹 Lambert 조명 공식

빛의 세기 = dot(-L, N)
  • L: 광원의 방향 벡터 (빛이 향하는 반대 방향)
  • N: 물체 표면의 법선 벡터(Normal)
  • 이 공식은 두 단위 벡터 간의 내적(dot product)으로, 결과값은 cos(θ)와 동일하다.
    • θ = 두 벡터의 사이각
    • dot = 1 → 정면
    • dot = 0 → 수직
    • dot < 0 → 음영

🧾 용어 정리

용어설명
Diffuse Light물체 표면에서 산란되는 조명. 일반적인 반사광
Lambert 법칙코사인 값으로 빛의 세기를 계산하는 조명 모델
LightDir빛의 방향 벡터 (빛이 향하는 반대 방향으로 사용됨)
LightDiffuse광원 자체의 색상. 빨강/파랑/흰색 등
MaterialDiffuse물체가 광원을 얼마나 받아들이는가를 나타냄
DiffuseMap물체 표면에 적용되는 텍스처 이미지
Normalize()벡터를 길이 1로 만드는 함수. 내적 계산 시 필수
dot()두 벡터의 내적. 각도에 따른 밝기 표현에 사용됨

🎨 Shader 작성 - Lighting_Diffuse.fx

1. 기본 설정 및 변수 선언

#include "00. Global.fx"

float3 LightDir;
float4 LightDiffuse;
float4 MaterialDiffuse;
Texture2D DiffuseMap;
  • LightDir: 광원의 방향 (반대 방향으로 내적)
  • LightDiffuse: 빛의 색상 (예: 흰색이면 전체 밝기)
  • MaterialDiffuse: 재질이 빛을 얼마나 받아들이는가
  • DiffuseMap: 물체 표면 텍스처 (기존 Texture0에서 이름 변경됨)

참고: 이전 Ambient 조명에서는 LightAmbient, MaterialAmbient를 사용했지만,
Diffuse에서는 이를 제거하고 위 3종 세트만 사용


2. 정점 셰이더(VS)

VertexOutput VS(VertexTextureNormal input)
{
    VertexOutput output;
    output.position = mul(input.position, W);        // 월드 변환
    output.position = mul(output.position, VP);      // 뷰-프로젝션 변환
    output.uv = input.uv;                             // 텍스처 좌표
    output.normal = mul(input.normal, (float3x3)W);   // 노멀은 회전만 적용
    return output;
}
  • 정점 위치를 월드 → 뷰 → 클립 공간으로 변환
  • 노멀은 회전만 필요하므로 float3x3 W로 변환

3. 픽셀 셰이더(PS)

float4 PS(VertexOutput input) : SV_TARGET
{
    float4 color = DiffuseMap.Sample(LinearSampler, input.uv);

    float value = dot(-LightDir, normalize(input.normal)); // Lambert 공식 적용
    color = color * value * LightDiffuse * MaterialDiffuse;

    return color;
}
  • 내적 결과값(value)이 조명의 세기(밝기)를 결정
  • 최종 색상 = 텍스처 색 × 밝기 비율 × 광원 색상 × 재질 색상

4. Technique 정의

technique11 T0
{
    PASS_VP(P0, VS, PS);
};
  • 하나의 렌더링 파이프라인 설정: 정점 셰이더 → 픽셀 셰이더

💻 C++ 구현 - DiffuseDemo.cpp

1. Init 함수

_shader = make_shared<Shader>(L"10. Lighting_Diffuse.fx");
  • 쉐이더 파일 등록
  • AmbientDemo 클래스를 복사해서 DiffuseDemo로 만들고 이름을 맞춰줌
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -10.f });
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
  • 카메라 위치 조정 (뒤에서 물체를 보도록 설정)

2. 오브젝트 설정 (Sphere)

_obj = make_shared<GameObject>();
_obj->AddComponent(make_shared<MeshRenderer>());
_obj->GetMeshRenderer()->SetShader(_shader);
_obj->GetMeshRenderer()->SetMesh(RESOURCES->Get<Mesh>(L"Sphere"));
_obj->GetMeshRenderer()->SetTexture(RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg"));
  • 구체 메시 + 셰이더 + 텍스처 연결

3. 오브젝트 설정 (Cube)

_obj2 = make_shared<GameObject>();
_obj2->GetOrAddTransform()->SetPosition(Vec3{ 0.5f, 0.f, 2.f });
_obj2->AddComponent(make_shared<MeshRenderer>());
_obj2->GetMeshRenderer()->SetShader(_shader);
_obj2->GetMeshRenderer()->SetMesh(RESOURCES->Get<Mesh>(L"Cube"));
_obj2->GetMeshRenderer()->SetTexture(RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg"));
  • 큐브 메시 추가, 오른쪽 위로 배치

4. MeshRenderer 내부 수정

_shader->GetSRV("DiffuseMap")->SetResource(_texture->GetComPtr().Get());
  • 기존 Texture0DiffuseMap으로 이름 변경 필요

5. Render 설정

RENDER->Init(_shader);
  • 렌더링 매니저에 셰이더 등록

좋습니다. 이어서 "Diffuse 조명 강의 교재 2부"를 작성해드리겠습니다.
이번 파트는 실습 중심으로 Update 함수에서 조명 세팅, 다양한 테스트 케이스, 그리고 Ambient 조명과의 조합까지 모두 포함한 완전한 내용입니다.


🛠 Update 함수: 조명 값 전달

Diffuse 조명은 빛의 방향, 광원의 색상, 재질이 받아들이는 퍼센티지에 따라 결과 색상이 달라진다.
따라서 DiffuseDemo의 Update() 함수에서는 HLSL 셰이더에 이 값들을 매 프레임 전달해줘야 한다.

void DiffuseDemo::Update()
{
    _camera->Update();
    RENDER->Update();

1. 광원 색상 설정

Vec4 lightDiffuse{ 1.f, 1.f, 1.f, 1.f };
_shader->GetVector("LightDiffuse")->SetFloatVector((float*)&lightDiffuse);
  • 광원 자체의 색
  • (1,1,1,1)은 흰색을 의미 (모든 색을 갖고 있음)

2. 빛의 방향 설정

Vec3 lightDir{ 1.f, -1.f, 1.f };
lightDir.Normalize();
_shader->GetVector("LightDir")->SetFloatVector((float*)&lightDir);
  • 오른쪽 아래 깊은 방향에서 빛이 들어오도록 설정
  • Normalize()로 반드시 단위 벡터로 정규화해야 dot() 내적 시 올바른 값 계산 가능

3. 재질 퍼센트 설정 (MaterialDiffuse)

Vec4 material(1.f);  // 모든 색을 다 받음
_shader->GetVector("MaterialDiffuse")->SetFloatVector((float*)&material);

_obj->Update();
_obj2->Update();
  • 재질이 광원을 얼마나 받아들이는지를 결정
  • 여기서는 모든 색(RGB+A) 다 받도록 설정

🔍 테스트 실습: 다양한 값 조합 실험

Diffuse 조명은 실험을 통해 감각적으로 익히는 것이 중요하다.
아래는 다양한 조합으로 테스트한 결과를 기반으로 정리한 예시이다.


✅ 테스트 1: 기본 흰빛, 모든 색 수용

Vec3 lightDir{ 1.f, -1.f, 1.f };
Vec4 lightDiffuse{ 1.f, 1.f, 1.f, 1.f };
Vec4 material{ 1.f, 1.f, 1.f, 1.f };
  • 오른쪽 아래에서 흰색 빛이 들어오며
  • 모든 색을 수용하므로 물체 본래 색상이 잘 드러난다

🟢 결과:

  • 구체는 명암이 자연스럽게 표현됨
  • 큐브는 각 면의 Normal이 일정하기 때문에 차이가 작게 나타남

✅ 테스트 2: 빛의 방향을 오른쪽으로만

Vec3 lightDir{ 1.f, 0.f, 0.f };
  • 오른쪽에서만 빛이 들어온다

🟢 결과:

  • 카메라가 앞을 보기 때문에 오른쪽 면이 그림자 지며 보이지 않음
  • 왼쪽 면만 빛이 반사됨

✅ 테스트 3: 빛 색상만 빨간색으로

Vec4 lightDiffuse{ 1.f, 0.f, 0.f, 0.f };

🟢 결과:

  • 광원 색이 빨간색이라, 결과적으로 빨간 색조가 강조된 화면이 출력됨

✅ 테스트 4: 재질이 빨간색만 받도록 설정

Vec4 material{ 1.f, 0.f, 0.f, 0.f };
Vec4 lightDiffuse{ 1.f, 1.f, 1.f, 1.f };

🟢 결과:

  • 빛은 흰색이지만, 재질이 R만 받으므로 붉은색으로 출력됨

✅ 테스트 5: 둘 다 빨간색으로

Vec4 lightDiffuse{ 1.f, 0.f, 0.f, 0.f };
Vec4 material{ 1.f, 0.f, 0.f, 0.f };

🟢 결과:

  • 더욱 진한 빨강으로 출력됨

🌘 어두운 면도 보이게 하려면? (Ambient 조명 병행 필요)

현재는 Lambert 법칙만 적용되어 빛을 못 받는 면은 완전히 검정색이 된다.
하지만 실제 현실에서는 빛이 반사되어 간접광이 도달하므로, 어느 정도는 보인다.

이를 해결하기 위해 Ambient 조명을 함께 합쳐야 한다.


Ambient 조명 특징

  • 방향 무관
  • 모든 픽셀에 일정한 밝기를 부여
  • Ambient + Diffuse + Specular로 병합 가능

병합 시 주의사항

  • Lambert, Specular은 곱셈 기반 → 비율 조절
  • Ambient는 덧셈 기반 → 단순 추가
  • 최종 조명 계산식 예:
float4 finalColor = ambientColor + diffuseColor + specularColor;
  • 이때 조명 강도가 모두 1.0이면 흰색으로 포화됨 → 각 조명별 0.2 ~ 0.6로 조절

📌 실전 구현

항목내용
광원 방향 설정LightDirNormalize()해서 내적 계산에 사용
광원 색상 설정LightDiffuse로 조명의 색 표현
재질 수용도MaterialDiffuse로 어떤 색을 얼마나 받아들이는지 조절
실제 색상 출력텍스처 × 밝기 × 광원 색상 × 재질 색상
그림자 영역 처리Ambient 조명 병합 필요 (덧셈 방식)
정점 회전 보정노멀 벡터는 float3x3 W로 회전만 적용

✅ 핵심

  • Diffuse 조명은 가장 기본적인 실제 조명 구현 모델로, 모든 실시간 렌더링의 기초가 된다.
  • dot(-LightDir, Normal) 공식은 반드시 숙지해야 하며, 이를 기반으로 각도에 따른 빛의 세기를 계산한다.
  • 빛의 방향, 광원 색상, 재질 색상 세팅만으로도 다양한 시각적 표현이 가능하다.
  • 그림자처럼 어두운 부분은 Ambient 조명과 병합하여 처리할 수 있으며, 추후 GUI(예: ImGui)를 통해 실시간 조정도 가능하다.
  • 이 조명 방식은 향후 Specular, Phong, Blinn-Phong, PBR 조명 구현의 기초가 된다.

profile
李家네_공부방

0개의 댓글