수업
✅ 주제
- 본 강의의 주제는 Ambient / Diffuse / Specular / Emissive 네 가지 조명을 하나의 구조로 통합하고,
- 이를 바탕으로 Shader와 RenderManager 간 조명-재질 정보 전달 구조를 확립하는 것이다.
- 이를 통해 향후 다중 광원이나 다양한 재질 반응 시스템 구현을 위한 통합 조명 프레임워크 기반을 마련한다.
📘 개념
- 기존에는 조명별로 셰이더 파일을 따로 작성했으나, 이제는 하나의 쉐이더에서 조명 종류별 연산을 함수로 처리하고,
- ConstantBuffer를 통해 구조체 단위로 값들을 GPU에 전달하여, 코드 재사용성과 유지 보수성을 향상시킨다.
- 핵심 구성 요소는 다음과 같다:
🔷 핵심 구조 정리
| 요소 | 목적 |
|---|
| Light.fx | 네 가지 조명을 연산하는 공용 쉐이더 함수 및 구조체 정의 |
| Lighting.fx | ComputeLight 호출만 수행하는 실제 렌더링 쉐이더 |
| RenderManager | 조명 및 재질 데이터를 셰이더로 밀어 넣는 역할 |
| LightingDemo.cpp | 오브젝트마다 조명/재질 데이터를 지정 및 연동 |
🧾 용어 정리
| 용어 | 설명 |
|---|
| Ambient Light | 기본 환경광. 그림자 없이 은은하게 조명 |
| Diffuse Light | 람베르트 반사 모델. 빛 방향과 노멀 각도 기반 |
| Specular Light | 퐁 반사 모델. 반사 벡터와 시선 벡터 일치 시 하이라이트 |
| Emissive Light | 외곽선 강조용 조명. 노멀과 시선이 수직일수록 강조 |
| LightDesc / MaterialDesc | 조명/재질 정보를 담은 구조체 |
| ConstantBuffer | 구조체 정보를 GPU로 전달하는 상수 버퍼 |
| ComputeLight() | 네 가지 조명 연산을 통합 처리하는 함수 |
💻 코드 분석
📁 1. 조명 연산 쉐이더 – 00. Light.fx
🔸 구조체 선언
struct LightDesc {
float4 ambient;
float4 diffuse;
float4 specular;
float4 emissive;
float3 direction;
float padding; // 16바이트 정렬
};
struct MaterialDesc {
float4 ambient;
float4 diffuse;
float4 specular;
float4 emissive;
};
- 조명 및 재질 구조체를 분리하여 관리
float3 뒤에 반드시 float padding 삽입 → 16바이트 정렬
🔸 ConstantBuffer 정의
cbuffer LightBuffer {
LightDesc GlobalLight;
};
cbuffer MaterialBuffer {
MaterialDesc Material;
};
- Global 조명과 오브젝트 재질을 각각 따로 관리 → 효율적인 상태 갱신
🔸 Texture & Compute 함수
Texture2D DiffuseMap;
float4 ComputeLight(float3 normal, float2 uv, float3 worldPosition)
{
float4 ambientColor = 0;
float4 diffuseColor = 0;
float4 specularColor = 0;
float4 emissiveColor = 0;
// Ambient
{
float4 color = GlobalLight.ambient * Material.ambient;
ambientColor = DiffuseMap.Sample(LinearSampler, uv) * color;
}
// Diffuse
{
float value = dot(-GlobalLight.direction, normalize(normal));
float4 color = DiffuseMap.Sample(LinearSampler, uv);
diffuseColor = color * value * GlobalLight.diffuse * Material.diffuse;
}
// Specular
{
float3 R = normalize(GlobalLight.direction - 2 * normal * dot(GlobalLight.direction, normal));
float3 E = normalize(CameraPosition() - worldPosition);
float value = saturate(dot(R, E));
float specular = pow(value, 10);
specularColor = GlobalLight.specular * Material.specular * specular;
}
// Emissive
{
float3 E = normalize(CameraPosition() - worldPosition);
float value = saturate(dot(E, normal));
float emissive = pow(smoothstep(0.0f, 1.0f, 1.0f - value), 2);
emissiveColor = GlobalLight.emissive * Material.emissive * emissive;
}
return ambientColor + diffuseColor + specularColor + emissiveColor;
}
- 모든 조명 연산을 하나의 함수로 통합
dot, smoothstep, pow 등을 조합하여 각 조명 효과 구현
📄 2. 통합 셰이더 파일 – 13. Lighting.fx
#include "00. Global.fx"
#include "00. Light.fx"
MeshOutput VS(VertexTextureNormal input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position.xyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
return output;
}
float4 PS(MeshOutput input) : SV_TARGET
{
return ComputeLight(input.normal, input.uv, input.worldPosition);
}
technique11 T0 {
PASS_VP(P0, VS, PS);
};
- 조명 연산은
ComputeLight에 위임
Lighting.fx는 구조만 담당 → 재사용성 극대화
📁 3. RenderManager 설정
선언
LightDesc _lightDesc;
shared_ptr<ConstantBuffer<LightDesc>> _lightBuffer;
ComPtr<ID3DX11EffectConstantBuffer> _lightEffectBuffer;
MaterialDesc _materialDesc;
shared_ptr<ConstantBuffer<MaterialDesc>> _materialBuffer;
ComPtr<ID3DX11EffectConstantBuffer> _materialEffectBuffer;
Init()
_lightBuffer = make_shared<ConstantBuffer<LightDesc>>();
_lightBuffer->Create();
_lightEffectBuffer = _shader->GetConstantBuffer("LightBuffer");
_materialBuffer = make_shared<ConstantBuffer<MaterialDesc>>();
_materialBuffer->Create();
_materialEffectBuffer = _shader->GetConstantBuffer("MaterialBuffer");
Push 함수
void RenderManager::PushLightData(const LightDesc& desc) {
_lightDesc = desc;
_lightBuffer->CopyData(_lightDesc);
_lightEffectBuffer->SetConstantBuffer(_lightBuffer->GetComPtr().Get());
}
void RenderManager::PushMaterialData(const MaterialDesc& desc) {
_materialDesc = desc;
_materialBuffer->CopyData(_materialDesc);
_materialEffectBuffer->SetConstantBuffer(_materialBuffer->GetComPtr().Get());
}
- 구조체 데이터를 버퍼에 복사 → GPU에 전달
📁 4. LightingDemo 적용
Update()
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.5f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(1.f);
lightDesc.emissive = Vec4(1.f);
lightDesc.direction = Vec3(0.f, -1.f, 0.f);
RENDER->PushLightData(lightDesc);
}
{
MaterialDesc desc;
desc.ambient = Vec4(0.2f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
desc.emissive = Vec4(0.3f, 0.f, 0.f, 1.f);
RENDER->PushMaterialData(desc);
_obj->Update();
}
{
MaterialDesc desc;
desc.ambient = Vec4(0.5f);
desc.diffuse = Vec4(1.f);
RENDER->PushMaterialData(desc);
_obj2->Update();
}
- 오브젝트마다 재질 값 개별 지정
- 큐브는 Ambient 강화로 전체 발광 효과 시도 가능
🔍 테스트 및 실습
| 실험 | 결과 |
|---|
| ambient = 0 | 어두운 곳 완전 검정 |
| ambient = 0.5 | 전체적으로 은은하게 밝음 |
| emissive 값 증가 | 외곽선 강조 증가 |
| specular = 0 | 하이라이트 없음 |
✅ 핵심
- 조명 구조체 기반 통합 시스템을 구현
- 각 조명 모델의 수식을 함수화하여 ComputeLight에서 처리
- 16바이트 정렬 준수 필수 → float3 뒤에는 padding
- RenderManager를 통해 Light/Material 구조체 데이터를 정확히 셰이더에 전달
- 실습을 통해 Ambient / Diffuse / Specular / Emissive 효과를 자유롭게 실험 가능