프로그래밍 공부
작성일
2023. 11. 10. 15:50
작성자
WDmil
728x90

UI는 3D화면에 2DObject를 그리는 행위이다.

 

그렇다면, UI를 그릴때. 중첩되서 표시되는경우 어떻게 해야할까.

 

보통은 z값을 0.0001정도씩 나누어서 표시하여 UI의 겹침을 막을 수 있다.

 

그러나, Render시. Object의 렌더순서에 따라 UI의 깊이를 나누어서 더 쉽고 간단하게 출력해줄 수 있다.

 


DepthStencillState

DirectX에서는 데이터를 출력할 때 깊이값을 변경하여. 2D객체가 출력될 때. 출력순서에 따라 깊이buffer를 0~1사이로 설정하여 어떤 객체가 덮어씌워지고 덮어씌워지지 않을지 선택하여 출력이 가능하다.

 

깊이 스텐실 버퍼의 순서는 대략 깊이버퍼 설정 -> 깊이스텐실 테스트 -> 테스트 결과 로 나타나는데.

 

각각은 다음과 같다.

 

  1. 깊이 버퍼
    • 깊이 버퍼는 화면의 각 픽셀에 대한 깊이값을 저장하는 버퍼로, 0~1사이값으로 표현된다.
    • 카메라에서 얼마나 떨어져있는지를 나타낸다.
  2. 깊이 스텐실 테스트
    • 렌더되려는 픽셀과 렌더되어있는 픽셀의 깊이값을 비교하고. 어떤것이 더 앞에있는지 여부를 결정한다.
    • 스텐실은 그림자, 반사 등 효과를 적용할때 사용하는 Buffer이다.
  3. 테스트 결과는 3가지로 분류되는데 다음과 같다.
    • PASS
      • 픽셀의 깊이값이. 버퍼의 값보다 큰경우 화면에 그려진다.
    • FAIL
      • 픽셀의 깊이값이 버퍼의 값보다 작은경우 화면에 그려지지 않는다.
    • Stencil Pass
      • 깊이 테스트에는 통과하였지만 스텐실 테스트가 실패했을 때 사용한다.

 

객체로 확인해보자.

#pragma once

// 깊이 스텐실 상태를 관리하는 클래스입니다.
class DepthStencilState
{
public:
    DepthStencilState();
    ~DepthStencilState();

    // 현재 깊이 스텐실 상태를 설정합니다.
    void SetState();

    // 깊이 테스팅을 활성화 또는 비활성화합니다.
    void DepthEnable(bool value);

    // 깊이 버퍼에 대한 쓰기 마스크를 설정합니다.
    void DepthWriteMask(D3D11_DEPTH_WRITE_MASK value);

    // 깊이 스텐실 상태가 변경되었음을 표시합니다.
    void Changed();

private:
    // D3D11_DEPTH_STENCIL_DESC 구조체로 정의된 깊이 스텐실 상태를 저장하는 멤버 변수입니다.
    D3D11_DEPTH_STENCIL_DESC desc = {};

    // 현재 설정된 깊이 스텐실 상태를 나타내는 인터페이스 멤버 변수입니다.
    ID3D11DepthStencilState* state = nullptr;
};

위와같이 작성된 데이터를 살펴보자.

 

위 객체는 깊이 스텐실 버퍼의 상태를 설정하고. 임의의 구간에서 해당된 깊이스텐실 버퍼를 사용할 수 있게 만들어놓은 객체이다.

 

스텐실 desc를 생성한다음. 필요할때 설정하고 설정을 해제할 수 있다.

#include "Framework.h"

// 생성자: 초기 깊이 스텐실 상태를 설정합니다.
DepthStencilState::DepthStencilState()
{
    desc.DepthEnable = true;  // 깊이 테스팅을 활성화합니다.
    desc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;  // 모든 깊이 값을 쓰도록 설정합니다.
    desc.DepthFunc = D3D11_COMPARISON_LESS;  // 깊이 테스트의 비교 함수를 설정합니다.

    // 스텐실 상태를 설정합니다.
    desc.StencilEnable = true;
    desc.StencilReadMask = 0xff;
    desc.StencilWriteMask = 0xff;

    // 렌더순서기준 정면
 
    // 앞면 및 뒷면의 스텐실 오퍼레이션 및 비교 함수를 설정합니다.
    desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
    // 스텐실 테스트가 실패했을 때 의 처리 = 실패시 현재 스텐실 유지
    desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
    // 스텐실 테스트는 통과했지만, 깊이테스트에 실패한경우 = 스텐실값을 증가시킴
    desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
    // 스텐실 테스트가 성공한경우 = 스텐실값을 유지
    desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
    // 스텐실 테스트에 사용되는 비교함수 = 항상성공함.

    // 렌더순서기준 뒷면
    desc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
    desc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
    desc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
    desc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

    // 깊이 스텐실 상태가 변경되었음을 표시합니다.
    Changed();
}

// 소멸자: 할당된 리소스를 해제합니다.
DepthStencilState::~DepthStencilState()
{
    state->Release();
}

// 현재 깊이 스텐실 상태를 설정합니다.
void DepthStencilState::SetState()
{
    // 2DObject출력기준 최대깊이로 설정해버린다.
    // 이럼으로써 모든 객체를 동일한 위치에서 계산할 수 있게된다.
    DC->OMSetDepthStencilState(state, 1);
}

// 깊이 테스팅을 활성화 또는 비활성화합니다.
void DepthStencilState::DepthEnable(bool value)
{
    // 깊이스텐실을 사용할것 인지 아닌지 설정함.
    desc.DepthEnable = value;  // 깊이버퍼를 켜거나 끄도록 설정합니다.

    // 깊이 스텐실 상태가 변경되었음을 표시합니다.
    Changed();
}

// 깊이 버퍼에 대한 쓰기 마스크를 설정합니다.
void DepthStencilState::DepthWriteMask(D3D11_DEPTH_WRITE_MASK value)
{
    /*
    D3D11_DEPTH_WRITE_MASK_ZERO: 
    깊이 버퍼에 아무것도 쓰지 않음. 쓰기가 비활성화되며, 
    깊이 정보가 업데이트되지 않습니다.

    D3D11_DEPTH_WRITE_MASK_ALL: 
    모든 픽셀의 깊이 정보를 깊이 버퍼에 쓰는 것을 허용함. 
    깊이 정보가 업데이트됩니다.
    */
    desc.DepthWriteMask = value;

    // 깊이 스텐실 상태가 변경되었음을 표시합니다.
    Changed();
}

// 깊이 스텐실 상태가 변경되었을 때 호출되며, 변경된 상태를 적용합니다.
void DepthStencilState::Changed()
{
    if (state != nullptr)
        state->Release();  // 이전 상태를 해제합니다.

    // 새로운 깊이 스텐실 상태를 생성하여 설정합니다.
    DEVICE->CreateDepthStencilState(&desc, &state);
}

스텐실설정은 대부분 API형태로 이루어져 있음으로. 자세한 코드의 동작사항을 몰라도 사용할 수 있도록 되어있다.

 

 


함수포인터

함수또한 포인터 처럼 사용할 수 있는데, 이는 함수 자체에 주소값이 존재하고. 어떠한 함수를 호출할 때. 그 함수의 주소값을 가져와서 사용하기 때문이다.

 

#include <iostream>
#include <functional>

using namespace std;

// 전방선언
// 함수는 메모리를 가진다.
void Attack();
void Damage();

class Player
{
public:
    static void Attack()
    {
        cout << "Player Attack" << endl;
    }

    void Damage()
    {
        cout << "Player Damage" << endl;
    }
};
class Monster
{
public:
    void Attack()
    {
        cout << "Monster Attack" << endl;
    }
};
int main()
{
    // 반환형(* 변수이름)( 매개변수)
    void (*action)();

    {
        // 일반적으로 함수포인터를 사용하는 방식. 전역변수 선언형
        action = Attack;
        action();

        action = Damage;
        action();

        // 객체 안에 존재하지만 전역형 함수일경우.
        action = Player::Attack;
        action();
    }

    {
        // 객체 자체에 존재하는 함수를 사용하기 위해 사용하는 방법.
        void(Player:: * playerAction)();

        Player player;
        playerAction = &Player::Damage;

        (player.*playerAction)();

        // 위 방법을 #include <functional> 을 사용해서 더 쉽게 쓰는방식.
        function<void()> ObjectAction = bind(&Player::Damage, player);
        ObjectAction();
        Monster monster;
        ObjectAction = bind(&Monster::Attack, monster);
        ObjectAction();
    }


}

void Attack()
{
    cout << "Attack" << endl;
}

void Damage()
{
    cout << "Damage" << endl;
}

 

함수포인터는 위와같이 작동할 수 있다.

728x90