DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 1. 디퍼드 렌더링 파이프라인

Vfly 2025. 9. 2. 00:22

전체 코드 : 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


DirectX11 기반 디퍼드 렌더링 파이프라인 구현

개요

이전 포스트에서 처음에는 Forward 렌더링 방식으로 구현을 하다가 중간에 Deffered 렌더링 방식으로 방향을 바꿨다고 언급을 했었다.

 

다 만들고나니 실제로 배치한 빛이 몇개 없어서 Forward 렌더링 방식을 사용해도 상관은 없었지만, 그래도 한번 써보자 해서 Deffered 렌더링 방식을 계속 유지한 채로 마무리 지었다.

 

Forward와 Deffered의 차이점은 지난 포스트에서 언급했으니, 이번 포스트에서는 실제로 적용된 코드를 한번 살펴 보겠다

https://tobrother.tistory.com/146

 

[DirectX 11 Eternal Return 모작] 0. 렌더링 파이프라인 흐름

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client GitHub - HyangRim/DirectX11-Engine-ClientContribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.github.comDirectX 11 Deffered Rendering Pipeline이번

tobrother.tistory.com

 

 

G-Buffer 구성

G-Buffer(Geometry Buffer)는 디퍼드 렌더링의 핵심으로, 기하하적인 정보를 저장하는 여러개의 렌더 타겟이다.

 

G-Buffer 구조체

// GBuffer.fx
struct GBufferOutput
{
    float4 albedo : SV_Target0;      // 알베도(기본 색상)
    float4 normal : SV_Target1;      // 월드 노말
    float4 position : SV_Target2;    // 월드 위치 + 깊이
    float4 material : SV_Target3;    // 머티리얼 속성
};

 

G-Buffer는 여러개의 렌더 타겟으로 구성되어 각각 다른 정보를 저장

  • Target0 : Diffuse 색상과 투명도
  • Target1 : 표면 노말 벡터 ( 조명 계산용 )
  • Target2 : 월드 좌표와 깊이 값
  • Target3 : Material 정보

 

G-Buffer 출력 픽셸 셰이더

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;
}

 

 

디퍼드 라이팅 구현

Lighting 계산 함수

// DeferredLighting.fx
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;
}

 

디퍼드 라이팅 픽셸 셰이더

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);
    
    return float4(baseColor.rgb, baseColor.a) * 2.f;
}

 

Renderer별 디퍼드 처리 구현

RenderManager의 디퍼드 렌더링 관리

// RenderManager.cpp
void RenderManager::RenderGeometryPass(vector<shared_ptr<GameObject>>& _gameObjects)
{
    RenderMeshRendererDeferred(_gameObjects);
    RenderModelRendererDeferred(_gameObjects);
    RenderAnimRendererDeferred(_gameObjects);
}

void RenderManager::RenderDeferredLighting()
{
    if (m_deferredLightingShader == nullptr)
        return;
    
    // 디퍼드 라이팅 셰이더 실행
    // G-Buffer를 입력으로 받아 최종 라이팅 결과 출력
}

 

MeshRenderer 디퍼드 처리

void RenderManager::RenderMeshRendererDeferred(vector<shared_ptr<GameObject>>& _gameObjects)
{
    map<InstanceID, vector<shared_ptr<GameObject>>> cache;

    // 분류 단계 (투명 객체 제외)
    for (shared_ptr<GameObject>& gameObject : _gameObjects) {
        if (gameObject->GetMeshRenderer() == nullptr)
            continue;

        // 투명 객체는 제외 (나중에 포워드로 처리)
        if (auto material = gameObject->GetMeshRenderer()->GetMaterial()) {
            if (material->IsTransparent())
                continue;
        }
         
        const InstanceID instanceID = gameObject->GetMeshRenderer()->GetInstanceID();
        cache[instanceID].push_back(gameObject);
    }

    // 인스턴싱 렌더링
    for (auto& pair : cache) {
        const vector<shared_ptr<GameObject>>& vec = pair.second;
        const InstanceID instanceID = pair.first;

        for (int32 idx = 0; idx < vec.size(); ++idx) {
            const shared_ptr<GameObject>& gameObject = vec[idx];
            InstancingData data;
            data.m_world = gameObject->GetTransform()->GetWorldMatrix();
            AddData(instanceID, data);
        }

        shared_ptr<InstancingBuffer>& buffer = m_buffers[instanceID];

        // 디퍼드 렌더링 호출 (G-Buffer에 데이터 쓰기)
        vec[0]->GetMeshRenderer()->RenderInstancingDeferred(buffer, m_isShadowTech);
    }
}

 

Animation Renderer 디퍼드 처리

void RenderManager::RenderAnimRendererDeferred(vector<shared_ptr<GameObject>>& _gameObjects)
{
    map<InstanceID, vector<shared_ptr<GameObject>>> cache;

    // 분류 단계 (투명 객체 제외)
    for (shared_ptr<GameObject>& gameObject : _gameObjects) {
        if (gameObject->GetModelAnimator() == nullptr)
            continue;

        // 투명 객체는 제외
        auto modelAnimator = gameObject->GetModelAnimator();
        if (auto material = modelAnimator->GetMaterial()) {
            if (material->IsTransparent())
                continue;
        }

        const InstanceID instanceID = gameObject->GetModelAnimator()->GetInstanceID();
        cache[instanceID].push_back(gameObject);
    }

    // 인스턴싱 렌더링
    for (auto& pair : cache) {
        const vector<shared_ptr<GameObject>>& vec = pair.second;
        shared_ptr<InstancedTweenDesc> tweenDesc = make_shared<InstancedTweenDesc>();
        const InstanceID instanceID = pair.first;

        for (int32 idx = 0; idx < vec.size(); ++idx) {
            const shared_ptr<GameObject>& gameObject = vec[idx];
            InstancingData data;
            data.m_world = gameObject->GetTransform()->GetWorldMatrix();
            AddData(instanceID, data);

            // INSTANCING TWEEN 데이터 수집
            gameObject->GetModelAnimator()->UpdateTweenData();
            tweenDesc->tweens[idx] = gameObject->GetModelAnimator()->GetTweenDesc();
        }

        // G-Buffer 셰이더에 TweenDesc 전송
        if (vec[0]->GetModelAnimator()->GetShader()) {
            vec[0]->GetModelAnimator()->GetShader()->PushTweenData(*tweenDesc.get());
        }

        shared_ptr<InstancingBuffer>& buffer = m_buffers[instanceID];

        // 디퍼드 렌더링 호출
        vec[0]->GetModelAnimator()->RenderInstancingDeferred(buffer, m_isShadowTech);
    }
}

 

Model Renderer 디퍼드 처리

void ModelRenderer::RenderInstancingDeferred(shared_ptr<class InstancingBuffer>& _buffer, bool _isShadowTech)
{
    if (m_model == nullptr)
        return;

    // 기존 검증 로직 활용
    if (Super::Render(false) == false)
        return;

    // G-Buffer용 셰이더 사용
    auto geometryShader = m_material->GetShader();
    if (geometryShader == nullptr)
        return;

    // 기존 RenderInstancing과 유사한 로직
    DC->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    geometryShader->PushGlobalData(Camera::s_MatView, Camera::s_MatProjection);

    // 본 데이터 푸시 (기존 로직 유지)
    BoneDesc boneDesc;
    const uint32 boneCount = m_model->GetBoneCount();
    for (uint32 i = 0; i < boneCount; ++i)
    {
        shared_ptr<ModelBone> bone = m_model->GetBoneByIndex(i);
        boneDesc.transforms[i] = bone->m_transform;
    }
    geometryShader->PushBoneData(boneDesc);

    const auto& meshes = m_model->GetMeshes();
    for (auto& mesh : meshes)
    {
        if (mesh->m_material)
            mesh->m_material->Update();

        geometryShader->GetScalar("BoneIndex")->SetInt(mesh->m_boneIndex);

        mesh->m_vertexBuffer->PushData();
        mesh->m_indexBuffer->PushData();
        _buffer->PushData();

        geometryShader->DrawIndexedInstancedCurTech(m_pass, mesh->m_indexBuffer->GetCount(), _buffer->GetCount());
    }
}

 

 

셰이더 테크닉 구성

// FOW.fx
technique11 T0
{
    // G-Buffer 패스
    PASS_VP(P0, VS_Mesh, PS_GBuffer)
    PASS_VP(P1, VS_Model, PS_GBuffer)
    PASS_VP(P2, VS_Animation, PS_GBuffer)
}

technique11 DeferredLightingTech
{
    PASS_VP(P0, VS_Quad, PS_DeferredLightingWithFOW)
}

technique11 GBufferTech
{
    PASS_VP(P0, VS_Mesh, PS_GBuffer)
    PASS_VP(P1, VS_Model, PS_GBuffer)
    PASS_VP(P2, VS_Animation, PS_GBuffer)
}

 

위 코드의 성능상 이점

  1. 오버드로우 최적화 : 화면에 실제로 그려지는 픽셀만 Lighting 계산
  2. 인스턴싱 지원 : 동일한 모델의 대량 렌더링 최적화
  3. 복잡한 라이팅 : 다중 라이트 시나리오에서 안정적인 성능을 보여줌
  4. 메모리 효율성 : G-Buffer 재사용을 통한 메모리 최적화

 

결론

이번 프로젝트에서 구현한 DirectX11 기반 Deffered 렌더링 파이프라인은 다음과 같은 특징을 가진다.

  1. 완전한 G-Buffer 구성 : Albedo, Normal, Position, Material 정보 저장
  2. 다앙햔 Renderer 지원 : Mesh, Model, Animation Renderer별 최적화된 디퍼드 처리
  3. 인스턴싱 최적화 : 동일한 객체의 대량 렌더링 지원
  4. 확장 가능한 구조 : FOW(Fog of War)등 추가 효과 통합 가능

 

이러한 구현을 통해 복잡한 3D 게임 환경에서도 안정적이고 효율적인 렌더링 성능을 달성할 수 있었음.