전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client
GitHub - HyangRim/DirectX11-Engine-Client
Contribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.
github.com
이번에는 셰이더 프로그래밍과 Material 시스템에 대해 알아보겠다.
Shader란?
셰이더(Shader)는 GPU에서 실행되는 프로그램으로, 3D 그래픽스의 렌더링 파이프라인에서 정점(Vertex)과 픽셀(Fragment/Pixel)을 처리하는 역할을 한다.
- Vertex Shader : 정점의 위치변환, Lighting 계산
- Pixel Shader : 픽셀 색상 결정, 텍스쳐 샘플링
- Geometry Shader : 정점 데이터를 기반으로 새로운 Geometry 생성
Material란?
Material은 오브젝트의 표면 특성을 정의하는 데이터 집합으로, 셰이더와 텍스쳐, 그리고 다양한 렌더링 속성들을 포함한다.
Material 시스템 구현
Material 클래스 구조
enum class RenderQueue
{
Opaque, // 불투명 객체
Cutout, // 알파 테스트용
Transparent, // 투명 객체
Max
};
enum class RenderingMode
{
Forward, // Forward 렌더링
Deferred, // Deferred 렌더링
Transparent // 투명 렌더링
};
class Material : public ResourceBase
{
public:
Material();
virtual ~Material();
// 쉐이더 설정
void SetShader(shared_ptr<Shader> _shader);
shared_ptr<Shader> GetShader() { return m_shader; }
// 텍스처 설정
void SetDiffuseMap(shared_ptr<Texture> _diffuseMap) { m_diffuseMap = _diffuseMap; }
void SetNormalMap(shared_ptr<Texture> _normalMap) { m_normalMap = _normalMap; }
void SetSpecularMap(shared_ptr<Texture> _specularMap) { m_specularMap = _specularMap; }
// 렌더링 모드 설정
void SetRenderingMode(RenderingMode _mode) { m_renderMode = _mode; }
void SetRenderQueue(RenderQueue _renderQueue) { m_renderQueue = _renderQueue; }
void SetTransparent(bool _transparent) { m_isTransparent = _transparent; }
private:
MaterialDesc m_desc;
RenderQueue m_renderQueue = RenderQueue::Opaque;
RenderingMode m_renderMode = RenderingMode::Deferred;
bool m_isTransparent = false;
shared_ptr<Shader> m_shader;
shared_ptr<Texture> m_diffuseMap;
shared_ptr<Texture> m_normalMap;
shared_ptr<Texture> m_specularMap;
};
HLSL 셰이더 시스템
글로벌 셰이더 설정 (Global.fx)
// 상수 버퍼 정의
cbuffer GlobalBuffer
{
matrix V; // View 행렬
matrix P; // Projection 행렬
matrix VP; // View-Projection 행렬
matrix Vinv; // View 역행렬
float3 CamPos; // 카메라 위치
float padding;
};
cbuffer TransformBuffer
{
matrix W; // World 행렬
};
// 공통 샘플러 상태
SamplerState LinearSampler
{
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap;
AddressV = Wrap;
};
// 블렌드 상태 정의
BlendState AlphaBlend
{
AlphaToCoverageEnable = false;
BlendEnable[0] = true;
SrcBlend[0] = SRC_ALPHA;
DestBlend[0] = INV_SRC_ALPHA;
BlendOp[0] = ADD;
};
Lighting 시스템 (Light.fx)
struct LightDesc
{
float4 ambient;
float4 diffuse;
float4 specular;
float4 emissive;
float3 direction;
float padding;
};
// 라이팅 계산 함수
float4 ComputeLight(float3 _normal, float2 _uv, float3 _worldPosition, float shadow = 0)
{
float4 ambientColor = 0;
float4 diffuseColor = 0;
float4 specularColor = 0;
float4 emissiveColor = 0;
float4 baseTexture = DiffuseMap.Sample(LinearSampler, _uv);
// Ambient 계산
ambientColor = baseTexture * GlobalLight.ambient * Material.ambient;
// Diffuse 계산
float value = saturate(dot(-GlobalLight.direction, normalize(_normal)));
diffuseColor = baseTexture * value * GlobalLight.diffuse * Material.diffuse;
// Specular 계산
float3 R = reflect(GlobalLight.direction, _normal);
float3 E = normalize(CameraPosition() - _worldPosition);
float specular = pow(saturate(dot(R, E)), 10);
specularColor = GlobalLight.specular * Material.specular * specular;
// 최종 색상 합성
float4 finalColor = ambientColor + (diffuseColor + specularColor) * shadow;
return finalColor;
}
최종 색상 공식은 퐁 셰이딩 식을 이용하였다.

Forward vs Deffered 렌더링 구현
Forward 렌더링은 각 객체를 순서대로 렌더링하며 즉시 Lighting을 계산한다.
// Forward 렌더링 픽셀 쉐이더
float4 PS_FOW(MeshOutput input) : SV_TARGET
{
// 그림자 계산
float shadow = CalcShadowFactor(ShadowMap, input.shadowPosH);
// 즉시 라이팅 계산
float4 baseColor = ComputeLight(input.normal, input.uv, input.worldPosition, shadow);
// FOW 효과 적용
float fogFactor = CalculateFogOfWar(input.worldPosition);
float3 finalColor = baseColor.rgb * max(fogFactor, 0.4f);
return float4(finalColor, baseColor.a);
}
Deffered 렌더링은 G-Buffer에 데이터를 저장한 후 나중에 계산한다.
struct GBufferOutput
{
float4 albedo : SV_Target0; // 알베도 색상
float4 normal : SV_Target1; // 노멀 벡터
float4 position : SV_Target2; // 월드 위치
float4 material : SV_Target3; // 머티리얼 속성
};
GBufferOutput PS_GBuffer(MeshOutput input)
{
GBufferOutput output;
// 텍스처 샘플링
float4 albedo = DiffuseMap.Sample(LinearSampler, input.uv);
if (albedo.a < 0.05f)
discard;
// G-Buffer 데이터 출력
output.albedo = albedo;
output.normal = float4(normalize(input.normal), 1.0f);
output.position = float4(input.worldPosition, input.position.z);
output.material = float4(1, 1, 1, 1);
return output;
}
G-Buffer의 데이터를 사용하여 Lighting을 계산하는 풀 스크린 패스
// 디퍼드 라이팅용 라이팅 계산
float4 ComputeDeferredLight(float4 albedo, float3 normal, float3 worldPos, float shadow)
{
float4 ambientColor = 0;
float4 diffuseColor = 0;
float4 specularColor = 0;
// Ambient
ambientColor = albedo * GlobalLight.ambient * shadow * 3.0f;
// Diffuse
float3 lightDir = -normalize(GlobalLight.direction);
float NdotL = saturate(dot(normal, lightDir));
diffuseColor = albedo * GlobalLight.diffuse * Material.diffuse * NdotL;
// Specular
if (NdotL > 0)
{
float3 reflectDir = reflect(-lightDir, normal);
float3 viewDir = normalize(CamPos - worldPos);
float spec = pow(saturate(dot(viewDir, reflectDir)), 10);
specularColor = GlobalLight.specular * Material.specular * spec;
}
// 최종 색상 계산
float4 finalColor = ambientColor + (diffuseColor + specularColor) * shadow;
return finalColor;
}
// 디퍼드 라이팅 + FOW 픽셀 셰이더
float4 PS_DeferredLightingWithFOW(VertexQuadOutput input) : SV_Target
{
int2 screenPos = (int2)(input.position.xy);
// G-Buffer에서 데이터 로드
float4 albedo = GBufferAlbedo.Load(int3(screenPos, 0));
float4 normalData = GBufferNormal.Load(int3(screenPos, 0));
float4 positionData = GBufferPosition.Load(int3(screenPos, 0));
if (albedo.a < 0.01f)
discard;
float3 worldPos = positionData.xyz;
float3 normal = normalize(normalData.xyz);
// 섀도우 계산
float4 shadowPosH = mul(float4(worldPos, 1.0f), ShadowTransform);
float shadow = CalcShadowFactor(ShadowMap, shadowPosH);
// 디퍼드 라이팅 계산
float4 baseColor = ComputeDeferredLight(albedo, normal, worldPos, shadow);
// FOW 계산 및 적용
float fogFactor = CalculateFogOfWar(worldPos);
// FOW 효과 적용
float3 grayColor = dot(baseColor.rgb, float3(0.299, 0.587, 0.114));
grayColor = grayColor * float3(0.7, 0.7, 0.8);
float grayIntensity = saturate(g_smoothness * 0.5f);
float3 foggedColor = lerp(baseColor.rgb, grayColor, (1.0f - fogFactor) * grayIntensity);
float minBrightness = max(g_darkness, 0.4f);
float3 finalColor = foggedColor * max(fogFactor, minBrightness);
return float4(finalColor, baseColor.a) * 2.f;
}
렌더링 시스템 통합
RenderManager에서의 분기 처리
void RenderManager::RenderGeometryPass(vector<shared_ptr<GameObject>>& _gameObjects)
{
// Deferred 렌더링: G-Buffer에 데이터 저장
RenderMeshRendererDeferred(_gameObjects);
RenderModelRendererDeferred(_gameObjects);
RenderAnimRendererDeferred(_gameObjects);
}
void RenderManager::RenderDeferredLighting()
{
if (m_deferredLightingShader == nullptr)
return;
// 풀스크린 쿼드로 라이팅 계산
m_deferredLightingShader->SetTechnique(L"DeferredLightingTech");
// G-Buffer 텍스처들을 바인딩
auto srv0 = GRAPHICS->GetGBufferSRV(0); // Albedo
auto srv1 = GRAPHICS->GetGBufferSRV(1); // Normal
auto srv2 = GRAPHICS->GetGBufferSRV(2); // Position
auto srv3 = GRAPHICS->GetGBufferSRV(3); // Material
// 라이팅 패스 실행
m_deferredLightingShader->DrawIndexed(0, 0, 6);
}
// 투명 객체는 Forward 렌더링
void RenderManager::RenderForward(vector<shared_ptr<GameObject>>& _gameObjects, bool _isShadowTech)
{
// 투명 객체들을 Forward 방식으로 렌더링
for (auto& obj : _gameObjects) {
if (obj->GetRenderer() && obj->GetRenderer()->GetMaterial()->IsTransparent()) {
obj->GetRenderer()->Render(_isShadowTech);
}
}
}
Camera에서의 객체 분류
void Camera::SortGameObjects()
{
shared_ptr<Scene> scene = CURSCENE;
const vector<shared_ptr<GameObject>>& gameObjects = scene->GetGameObjects();
m_vecForward.clear();
m_vecBackward.clear();
for (auto& object : gameObjects)
{
if (!object->GetActive()) continue;
shared_ptr<Renderer> renderer = object->GetRenderer();
if (renderer == nullptr) continue;
RenderQueue renderQueue = renderer->GetMaterial()->GetRenderQueue();
switch (renderQueue)
{
case RenderQueue::Opaque:
case RenderQueue::Cutout:
m_vecForward.push_back(object); // Deferred 렌더링 대상
break;
case RenderQueue::Transparent:
m_vecBackward.push_back(object); // Forward 렌더링 대상
break;
}
}
}
고급 셰이더 효과 구현
Fog of War 구현
float CalculateFogOfWar(float3 worldPos) {
float distance = length(worldPos - g_playerWorldPos);
if (distance > g_sightRange * 1.2f) {
return max(g_darkness, 0.4f);
}
float fogFactor = 1.0f;
if (distance > g_sightRange) {
fogFactor = max(g_darkness, 0.4f);
}
else if (distance > (g_sightRange - g_fadeDistance)) {
float fadeRatio = (distance - (g_sightRange - g_fadeDistance)) / g_fadeDistance;
fadeRatio = smoothstep(0.0f, 1.0f, fadeRatio);
fadeRatio = pow(fadeRatio, g_smoothness);
fogFactor = lerp(1.0f, max(g_darkness, 0.4f), fadeRatio);
}
return fogFactor;
}
아웃라인 포스트 프로세싱
아이템 박스 등의 특별한 객체에 외곽선 효과를 적용할때 씀
float4 PS_OutlinePost(VertexQuadOutput input) : SV_Target {
float2 screenSize = float2(1366, 768);
float2 texelSize = 1.0f / screenSize;
float3 normalSample[9];
float3 positionSample[9];
int idx = 0;
[unroll]
for (int y = -1; y <= 1; ++y) {
[unroll]
for (int x = -1; x <= 1; ++x) {
float2 offset = float2(x, y) * texelSize * 0.01f;
normalSample[idx] = gNormalBuffer.Sample(LinearSampler, input.uv + offset).xyz;
positionSample[idx] = gPositionBuffer.Sample(LinearSampler, input.uv + offset).xyz;
idx++;
}
}
// 외곽선 판정
float normalDiff = 0;
float positionDiff = 0;
[unroll]
for (int i = 0; i < 9; i++) {
if (i == 4) continue;
normalDiff += distance(normalSample[4], normalSample[i]);
positionDiff += distance(positionSample[4], positionSample[i]);
}
if (normalDiff > 0.1f || positionDiff > 0.5f) {
return float4(1, 1, 1, 1); // 외곽선 색상
}
return float4(0, 0, 0, 0); // 투명
}


UI ScrollView 셰이더
// UI용 클리핑 시스템
cbuffer ScrollViewClippingBuffer : register(b10)
{
float4 ScrollViewClippingRect; // x=left, y=top, z=right, w=bottom
bool ScrollViewEnableClipping;
float3 ScrollViewClippingPadding;
};
// 클리핑이 적용된 픽셀 셰이더
float4 PS_Clipped(VertexOutput2 input) : SV_TARGET
{
// 스크린 좌표로 변환
float2 screenPos = input.screenPos.xy / input.screenPos.w;
screenPos = screenPos * 0.5f + 0.5f;
screenPos.y = 1.0f - screenPos.y;
screenPos.x *= 1366.0f;
screenPos.y *= 768.0f;
// 클리핑 영역 체크
if (ScrollViewEnableClipping)
{
if (screenPos.x < ScrollViewClippingRect.x || screenPos.x > ScrollViewClippingRect.z ||
screenPos.y < ScrollViewClippingRect.y || screenPos.y > ScrollViewClippingRect.w)
{
discard;
}
}
float4 color = DiffuseMap.Sample(ImageSampler, input.uv);
return color;
}
// UI용 헬스바 쉐이더
float4 PS_HealthBar(VertexOutput2 input) : SV_TARGET
{
float4 color = DiffuseMap.Sample(ImageSampler, input.uv);
if (input.uv.x > HealthRatio)
{
return float4(0, 0, 0, 1);
}
return color;
}

셰이더 관리 시스템
Shader 클래스의 데이터 푸시 시스템
class Shader
{
public:
// 다양한 데이터를 셰이더에 전송하는 함수들
void PushGlobalData(const Matrix& _view, const Matrix& _projection);
void PushTransformData(const TransformDesc& _desc);
void PushLightData(const LightDesc& _desc);
void PushMaterialData(const MaterialDesc& _desc);
void PushFOWData(const FogOfWarData& _desc);
void PushHealthBarData(float _healthRatio, float _manaRatio, int _type = 0);
void PushScrollViewClippingData(const Vec4& clippingRect, bool enableClipping);
private:
// 각 데이터 타입별 상수 버퍼
shared_ptr<ConstantBuffer<GlobalDesc>> m_globalBuffer;
shared_ptr<ConstantBuffer<TransformDesc>> m_transformBuffer;
shared_ptr<ConstantBuffer<LightDesc>> m_lightBuffer;
shared_ptr<ConstantBuffer<MaterialDesc>> m_materialBuffer;
shared_ptr<ConstantBuffer<FogOfWarData>> m_fowBuffer;
shared_ptr<ConstantBuffer<HealthBarData>> m_healthBarBuffer;
shared_ptr<ConstantBuffer<ScrollViewClippingData>> m_scrollViewClippingBuffer;
};
// FOW 데이터 푸시 구현
void Shader::PushFOWData(const FogOfWarData& _desc)
{
if (m_fowBuffer == nullptr)
{
m_fowBuffer = make_shared<ConstantBuffer<FogOfWarData>>();
m_fowBuffer->Create();
m_fowEffectBuffer = GetConstantBuffer("FogOfWarData");
}
m_fowDesc = _desc;
m_fowBuffer->CopyData(m_fowDesc);
if (m_fowEffectBuffer)
m_fowEffectBuffer->SetConstantBuffer(m_fowBuffer->GetComPtr().Get());
}
결론
주요 구현 내용
- Material 시스템 : 렌더링 큐와 모드를 활용한 체계적인 렌더링 관리
- 전장의 안개 (FOW) 효과 : HLSL을 활용한 동적 가시성 제어 시스템
- 디퍼드 렌더링 : G-Buffer를 활용한 효율적인 Lighting 계산
- Forward/Deffered 분기 : 객체 특성에 따른 렌더링 파이프라인 자동 선택
- 포스트 프로세싱 : 외곽선 검출 등의 화면 후처리 효과 ( 소벨 마스크 )
- UI 셰이더 : 클리핑과 특수 효과가 적용된 UI 렌더링
'DirectX11 > Eternal Return 모작' 카테고리의 다른 글
| [DirectX 11 Eternal Return 모작] 6. Scene (0) | 2025.09.02 |
|---|---|
| [DirectX 11 Eternal Return 모작] 5. Component (0) | 2025.09.02 |
| [DirectX 11 Eternal Return 모작] 3. 인스턴싱 (0) | 2025.09.02 |
| [DirectX 11 Eternal Return 모작] 2. FOW ( Fog Of War ) (0) | 2025.09.02 |
| [DirectX 11 Eternal Return 모작] 1. 디퍼드 렌더링 파이프라인 (0) | 2025.09.02 |