픽셀쉐이더로 간단한 테스트를 해보려고 했더니, 텍스쳐 좌표를 버텍스에 추가하지 않은 것을 알게되었다.

이로인해, 간단하게 버텍스에 텍스쳐 좌표를 추가해주었다.

 

struct Vertex
{
	DirectX::SimpleMath::Vector3 Position;
	DirectX::SimpleMath::Vector3 Color;
	DirectX::SimpleMath::Vector3 Normal;
	DirectX::SimpleMath::Vector2 TexCoord;
};

 

먼저, 버텍스 구조체에 TexCoord 변수를 추가해주었다.

텍스쳐 좌표는 x,y만 있으면 되기떄문에 vector2로 선언해주었다.

 

이에맞게 인풋 레이아웃도 변경해주었다.

std::vector<D3D11_INPUT_ELEMENT_DESC> inputElements = 
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3 * 2, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 4 * 3 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0},
};

 

이렇게, TexCoord도 인풋레이아웃에 추가해주었다.

여기서 주의해야 할 점은 데이터 포멧을 꼭 맞춰주자....

 

본인은 TexCoord를 vector2로 선언한 것을 잊고 위에 데이터 포벳을 DXGI_FORMAT_R32G32B32_FLOAT로 설정하였는데, 이로 인해 렌더링이 제대로 안되는 상황을 겪었다..

혹시나 하고 보니까 데이터 포멧이 잘못되어있었고, 위 코드처럼 R32G32로 수정했더니, 아주 잘 되는 것을 확인할 수 있었다.

 

텍스쳐 좌표를 테스트하기 위해, 픽셀쉐이더에 간단한 코드를 추가하였다.

 

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
    float3 color : COLOR;
    float2 TexCoord : TEXCOORD;
};

float4 main(PixelShaderInput _Input) : SV_TARGET
{
    if(_Input.TexCoord.x > 0.5f)
    {
        return float4(1.0f, 0.0f, 0.0f, 1.0f);
    }
    else
    {
        return float4(0.0f, 0.0f, 1.0f, 1.0f);
    }
}

 

결과는 아래처럼 잘 나온다.

 

 

 

 

주어진 문자가 PPAP문자열인지 테스트하는 문제이다.

 

PPAP문자열의 규칙은 아래와 같다.

 

1. P는 PPAP문자열이다.

2. PPAP문자열에서 P 하나를 PPAP로 바꾼 것도 PPAP 문자열이다.

Ex) "PPAP"PAP ,  P"PPAP"AP , PPA"PPAP" 

 

어떤 문자열이 주어졌을 때, 해당 문자열이 PPAP인지 아닌지를 검사하면 된다.

문제 풀이

 

어떤 PPAP문자열에서 P를 PPAP로 바꾼 것이 PPAP문자열이라면, 반대로 현재 문자열에서 PPAP를 모두 P로 바꿨을 때, 해당 문자열이 PPAP문자열이라면 현재 문자열도 PPAP문자열이라고 역으로 추론할 수 있다.

 

예를 들어보자.

P -> PPAP -> PPPAPAP 라는 문자열이 있다고 했을 때, PPPAPAP 문자열이 입력으로 주어졌다고 해보자.

이 문자열에서 PPAP를 모두 지워보자.

 

P"PPAP"AP -> PPAP

"PPAP" -> P 

 

즉, PPAP문자열이 맞다면 문자열 내부의 PPAP를 모두 지우면 P가 나올 수 밖에 없다. 모든 PPAP문자열은 P로부터 시작하기 때문이다.

 

반대로 PPAP문자열이 아니라면?
PPAPAAP라는 문자열이 주어졌다고 해보자.

"PPAP"AAP -> PAAP 

PAAP 문자열에는 더이상 PPAP가 없기 때문에 더 제거할 수가 없다.

현재 상태의 문자열이 P가 아니므로, PPAPAAP는 PPAP문자열이 아니라고 추론할 수 있다.

 

그렇다면, PPAP를 어떻게 제거해야 할까?
그냥 std::string을 사용하여 제거하게 되면, 시간초과가 발생할 것이다. 왜냐하면, 문자열은 배열기반이기 때문에 삭제연산이 매우 느리기 때문이다.

 

그렇기 때문에, 이런 유형의 문자열 문제는 대부분 Stack을 사용하여 해결하게 된다.

예를들어 보자.

 

PPPAPAP 를 string을 사용해서 PPAP를 제거한다고 하면

PXXXXAP -> PPXXXAP -> PPAP -> XXXX -> P 이렇게, 삭제를 한 뒤에 앞으로 땡겨오는 작업이 필요하다.

문자열의 길이가 길어질수록 더 많은 연산량을 요구할 것이다.

 

하지만, stack에 문자열의 문자를 하나씩 삽입하며 검사하게 되면

PPPAP -> PP -> PPAP -> P 이렇게 문자열을 땡겨오는 연산 없이 중간에 PPAP를 제거하며 문제를 해결할 수 있게 된다.

 

스택을 활용하여 문제를 해결하는 예시를 보자.

이렇게, 입력 문자열과 스택이 있다고 해보자.

먼저 입력 문자열의 앞부터 삽입을 해보자.

이렇게 P가 삽입될 것이다. 현재 원소로 P가 삽입되면 스택에 PPAP가 순서대로 저장되어 있는지를 검사해야 한다.

(PPAP는 무조건 P로 끝나기 때문에 P가 삽입될 때마다 스택의 원소를 검사한다.)

 

현재는 스택의 Size가 1이므로 검사할 필요가 없다. 계속 삽입해보자.

5번째 문자까지 삽입하게 되면, 위와 같은 형태가 된다.

이 때, 스택에서 총 4개의 문자를 꺼내서 문자열을 만들어보면 PAPP가 된다.

스택은 문자열이 거꾸로 저장되기 때문에, PAPP라면, 실제 문자열에선 PPAP가 될 것이다.

 

즉, 스택에서 PPAP를 발견했기 때문에, 이를 제거해주어야 한다.

스택에서 꺼낸 PPAP를 다시 넣지 않고, P만을 삽입해주면 아래와 같아진다.

다시 남은 문자를 삽입해주자. 마지막까지 삽입해주면 아래와 같아진다.

다시 스택에서 4개의 문자를 꺼내 검사해보면, PAPP임을 확인할 수 있다.

이를 다시 삽입하지 않고, P만을 넣어주면 스택에는 원소 P만 남게 되고, 입력 문자열을 모두 검사하였으므로 최종적으로 스택에 남은 문자는 P 하나가 된다.

 

위에서 설명했듯이, PPAP를 모두 제거했을 때 P만 남는다면 해당 문자열은 PPAP문자열이기 때문에 현재 주어진 문자열은 PPAP문자열이 맞다고 추론할 수 있다.

 

풀이 코드

std::string Input;
std::cin >> Input;

std::stack<char> Stack;

 

문자열을 입력받아주고 스택을 선언하였다.

for (int i = 0; i < Input.size(); i++)
{
    Stack.push(Input[i]);

    if (Stack.size() >= 4 && Input[i] == 'P')
    {
        std::string Str = "";
        for (int j = 0; j < 4; j++)
        {
            Str.push_back(Stack.top());
            Stack.pop();
        }

        if(Str == "PAPP")
        {
            Str = "P";
        }

        for (int j = Str.size() - 1; j >= 0; j--)
        {
            Stack.push(Str[j]);
        }
    }
}

 

반복문을 돌며, 입력 문자열의 문자를 하나씩 스택에 넣어주었다.

현재 스택에 삽입한 문자가 P이고, 스택에 4개 이상의 원소가 있다면 위에서부터 4개의 원소를 꺼내 검사해주었다.

만약 스택에서 꺼낸 문자들이 PAPP라면 해당 문자를 제거하고, 스택에는 P를 다시 삽입해주었다.

만약 PAPP가 아니라면, 그대로 다시 stack에 넣어주었다.

 

if (Stack.size() == 1 && Stack.top() == 'P')
{
    std::cout << "PPAP";
}
else
{
    std::cout << "NP";
}

return 0;

 

반복문이 종료되었을 때, Stack에 남은 것이 'P' 하나라면, 현재 문자열은 PPAP문자열이 맞다고 판단하여 PPAP를 출력해주었고, 다른 경우엔 NP를 출력해주었다.

 

코드 전문

더보기
#include <iostream>
#include <vector>
#include <stack>

void Init()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
}

int main()
{
    Init();

    std::string Input;
    std::cin >> Input;

    std::stack<char> Stack;

    for (int i = 0; i < Input.size(); i++)
    {
        Stack.push(Input[i]);

        if (Stack.size() >= 4 && Input[i] == 'P')
        {
            std::string Str = "";
            for (int j = 0; j < 4; j++)
            {
                Str.push_back(Stack.top());
                Stack.pop();
            }

            if(Str == "PAPP")
            {
                Str = "P";
            }

            for (int j = Str.size() - 1; j >= 0; j--)
            {
                Stack.push(Str[j]);
            }
        }
    }

    if (Stack.size() == 1 && Stack.top() == 'P')
    {
        std::cout << "PPAP";
    }
    else
    {
        std::cout << "NP";
    }

    return 0;
}

 

 

과제의 목록과 마감기한이 주어졌을 때, 얻을 수 있는 최대 점수를 구하는 문제이다.

하나의 과제를 해결하는데 하루의 시간이 걸리기 때문에, 특정 과제를 해결하면 다른 과제를 해결할 수 없게 될 수도 있다.

이러한 상황에서, 최적의 과제 목록을 탐색하여 점수의 최대값을 구하는 문제이다.

 

문제 풀이

이 문제는 아이디어를 필요로 하는 그리디 문제이다. 

하지만, 아이디어를 떠올리기 어려운 문제는 아니다.

 

먼저 생각해보자. 모든 과제를 해결할 수 없고, 특정 과제만을 해결할 수 있다면 어떤 과제를 고르는게 베스트일까?

당연히 점수를 가장 많이 주는 과제를 해결하는 것이 베스트일 것이다.

 

하지만, 점수만 많이 준다고 무작정 먼저 해결해서는 안된다.

모든 문제에는 마감기한이 있다.

어떤 과제는 1일이 남았는데 60점을 주고, 어떤 과제는 2일이 남았는데 80점을 준다고 가정해보자.

 

이 상황에서, 두 번쨰 과제가 점수를 더 많이준다는 이유로 먼저 해버리면, 첫번째 과제는 해결하지 못한채로 총 80점을 받게 된다.

 

하지만, 첫번째 과제를 먼저 해결하게 된다면 두 번째 과제도 다음 날 해결할 수 있기 때문에 총 140점을 받게 된다.

그러므로, 무작정 점수가 높다고 먼저 해결하는 것이 아니라, 마감기한도 고려하여 우선순위를 두어야 한다.

마감기한이 오래남았다면, 최대한 마감기한에 가깝게 해결하는 것이 이상적일 것이다.

 

그렇다면 어떻게 문제를 해결해야 할까?

 

본인은 먼저 일수별로 해결할 과제를 저장하는 배열을 만들었다.

주어진 과제중 가장 긴 마감기한이 10일이라고 치면, 10개의 원소를 담는 배열을 만든 뒤, 각 원소에는 i일차에 해결할 과제를 저장하였다.

 

이후, 과제 목록을 점수순으로 정렬한 뒤, 가장 높은 점수를 주는 과제부터 확인하며 마감기한에 최대한 딱 맞게 배열에 저장해주었다.

 

마감기한이 3일인 과제인데, 이미 3일차에 해결할 문제가 저장되어 있다면 2일차에 해결하도록 하고, 만약 2일차에도 해결해야 할 문제가 저장되어 있다면 1일차에 하도록 하였다. 마찬가지로 1일차에도 저장되어있다면? 현재 과제는 버려도 되는 것으로 판단하고 해결하지 않도록 하였다.

 

그림을 보며 이해해보자. 입력은 아래와 같이 가정해보겠다.

4 60
4 40
1 20
2 50
3 30
4 10
6 5

  

 

위의 배열은 n일차에 해결할 과제를 저장할 배열이고, 아래 배열은 주어진 입력을 점수순으로 정렬한 상태이다.

아래의 배열중 위의 값은 마감기한이고, 아래의 값이 점수이다.

 

이 상황에서, 첫번째 원소를 확인해보자. 

마감기한이 4일이고 점수가 60점이다. 이 과제는 마감기한에 최대한 가깝게 해결하는 것이 좋기 때문에, 4일차에 배당해주도록 하겠다.

배열에는 점수만 저장해주었다. 다음 두 번째 과제를 보자. 마감기한은 2일이다. 그러므로 2일차에 배정해주겠다.

다음 세 번째 과제도 보자. 마감기한이 4일이기 때문에, 4일차에 배정을 해주어야 하지만, 4일차엔 이미 배정된 과제가 있다. 그러므로, 4일차보다는 하루 빠른 3일차에 배정을 해주도록 하겠다.

다음 4번째 과제를 보자. 마감기한이 3일이 남았는데, 3일차에는 과제가 배정되어 있다. 그러므로 2일차에 배정해야 하지만, 2일차에도 이미 과제가 배정되어 있다. 그러므로 해당 과제는 1일차에 배정해 줄 것이다.

5번째 과제는 1일차에 배정을 해주어야 하지만, 1일차에 이미 과제가 배정되어 있다. 1일보다 더 빠른 날짜에 과제를 해결할 수는 없기 떄문에, 5번째 과제는 버릴 것이다.

 

6번째과제도 마찬가지이다. 마감기한이 4일짜리인데, 4,3,2,1차 모두 과제가 배정되어있으므로 6번째 과제도 버려야 한다.

 

마지막 7번째 과제는 아래와 같이 배정할 수 있다.

 

이렇게, 해결할 과제를 모두 배정하였고, 총 점수의 합은 185점이 된다.

 

풀이 코드

Init();

int NumOfHW = 0;
std::cin >> NumOfHW;

std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::less<std::pair<int, int>>> Queue;

int MaxDeadLine = 0;

for (int i = 0; i < NumOfHW; i++)
{
    int DeadLine = 0;
    int Grade = 0;
    std::cin >> DeadLine >> Grade;

    Queue.push({ Grade, DeadLine });

    MaxDeadLine = std::max(DeadLine, MaxDeadLine);
}

먼저, 과제의 수를 입력받았다.

이후, 우선순위 큐에 입력받을 과제를 {점수, 마감기한} 페어로 삽입해주었다.

 

그리고, 주어진 과제의 마감기한 중 가장 긴 마감기한을 MaxDeadLine에 저장해주었다.

이 값은 해결할 과제를 저장할 배열을 효율적으로 resize하기 위해 구해주었다.

 

std::vector<int> HomeWorks(MaxDeadLine + 1,  0);

while (Queue.size() > 0)
{
    std::pair<int, int> CurHomeWork = Queue.top();
    Queue.pop();

    int CurIndex = CurHomeWork.second;

    while (CurIndex > 0)
    {
        if (HomeWorks[CurIndex] == 0)
        {
            HomeWorks[CurIndex] = CurHomeWork.first;
            break;
        }

        CurIndex--;
    }
}

 

HomeWork배열은 해결할 숙제를 저장할 배열이다. 해당 배열의 i번째 인덱스의 원소는 i일차에 해결할 과제의 점수이다.

0번 인덱스는 사용하지 않는다.

 

다음은 점수순으로 정렬된 우선순위 큐에서 가장 위의 원소를 꺼내주어, 해당 원소를 HomeWork배열에 등록해주었다.

마감일자에 해당하는 인덱스에 과제가 배정되어 있지 않다면, 해당 인덱스에 점수를 저장해주었고 만약 과제가 배정되어 있다면, 인덱스를 1씩 빼며 배정할 수 있는 자리가 있나 탐색해주었다.

 

이 과정을 우선순위 큐가 모두 빌 때까지 반복하였다.

int GradeSum = 0;

for (int i = 0; i < HomeWorks.size(); i++)
{
    GradeSum += HomeWorks[i];
}

std::cout << GradeSum;

return 0;

 

이후, 배정된 과제 배열의 점수를 모두 더해준 뒤 출력해주면 끝이다.

 

풀이 코드

더보기
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

void Init()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
}

int main()
{
    Init();

    int NumOfHW = 0;
    std::cin >> NumOfHW;

    std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::less<std::pair<int, int>>> Queue;

    int MaxDeadLine = 0;

    for (int i = 0; i < NumOfHW; i++)
    {
        int DeadLine = 0;
        int Grade = 0;
        std::cin >> DeadLine >> Grade;

        Queue.push({ Grade, DeadLine });

        MaxDeadLine = std::max(DeadLine, MaxDeadLine);
    }

    std::vector<int> HomeWorks(MaxDeadLine + 1,  0);

    while (Queue.size() > 0)
    {
        std::pair<int, int> CurHomeWork = Queue.top();
        Queue.pop();

        int CurIndex = CurHomeWork.second;
        
        while (CurIndex > 0)
        {
            if (HomeWorks[CurIndex] == 0)
            {
                HomeWorks[CurIndex] = CurHomeWork.first;
                break;
            }

            CurIndex--;
        }
    }

    int GradeSum = 0;

    for (int i = 0; i < HomeWorks.size(); i++)
    {
        GradeSum += HomeWorks[i];
    }

    std::cout << GradeSum;

    return 0;
}

 

 

 

여러 친구관계가 주어졌을 때, A->B->C->D->E의 관계가 존재하는지를 탐색하는 문제이다.

하나의 친구에서 4개의 관계를 걸쳐 다른 친구로 통할 수 있는 경로가 있는가를 묻는 것이다.

문제 풀이

DFS를 사용하면 어렵지 않게 해결할 수 있다.

친구관계를 노드라고 생각해보자.

위 그림과 같은 친구관계가 주어진다면, 정답이 될 수 있는 경우의 수는 1-2-4-5-6, 3-2-4-5-6 이 있을 것이다.

이 경우의 수를 찾기 위해선, 노드를 타고가며 탐색을 해야한다.

 

DFS를 이용하여, 깊이를 증가시키며 연결된 노드를 타고가다 보면, 깊이가 4가 되는 구간을 찾을 수 있다.

깊이가 4가 되는 구간이 있다면, 그 즉시 DFS를 종료하고 1을 반환하면 된다. (더이상 DFS를 할 필요가 없다.)

 

반면, 끝까지 DFS를 했음에도, 깊이가 4가 되는 구간을 찾지 못했다면 0을 반환하면 된다.

 

이 문제에서 주의해야할 점은, 모든 점을 기준으로 DFS를 다 해야한다는 것이다.

예를 들어보자.

 

1-2-4-5-6은 분명 깊이가 4가 되는 구간이다.

이 구간을 1부터 시작해서 DFS를 탐색하면 깊이가 4가되는 구간을 발견할 수 있지만, 2,4,5부터 시작하면 깊이가 4가되는 구간을 발견할 수가 없다.

 

그러므로, 1-2-4-5-6에 대해 모두 DFS를 탐색해주어야 하는 것이다.

중간에 깊이가 4가되는 지점을 발견한다면, 그 즉시 DFS를 멈추어도 되지만, 발견하지 못했다면 모든 친구를 시작점으로 하여 DFS를 진행하여야 한다.

 

풀이 코드

int NumOfMan = 0;
std::cin >> NumOfMan;

int NumOfRelationShip = 0;
std::cin >> NumOfRelationShip;

std::vector<std::vector<int>> RelationShip(NumOfMan);
for (int i = 0; i < NumOfRelationShip; i++)
{
	int First = 0;
	int Second = 0;
	std::cin >> First >> Second;

	RelationShip[First].push_back(Second);
	RelationShip[Second].push_back(First);
}

 

먼저, 사람의 수와 입력될 관계의 수를 입력받아 저장해주었고, 입력되는 관계를 벡터에 모두 저장해주었다.

관계는 양방향이기 때문에, 반드시 두 쪽에 모두 저장해주어야 한다.

isVisit.resize(NumOfMan, false);

 

다음은 방문체크 배열을 resize해주었다. DFS에 이용할 것이다.

for (int i = 0; i < NumOfMan; i++)
{
    DFS(RelationShip, i, 0);

    if (isFind == true)
    {
        break;
    }
}

 

다음은 DFS를 모든 사람을 시작으로 실행해주고 있다.

다만, 깊이가 4 이상이 되는 지점을 발견한다면 isFind를 true로 만들어줄것이고, isFind가 true가 되면 반복문을 즉시 탈출하도록 하였다.

void DFS(std::vector<std::vector<int>>& _RelationShip, int _CurIndex, int _Depth)
{
    if (_Depth == 4)
    {
        isFind = true;
        return;
    }

    isVisit[_CurIndex] = true;

    for (int i = 0; i < _RelationShip[_CurIndex].size(); i++)
    {
        int NextIndex = _RelationShip[_CurIndex][i];

        if (isVisit[NextIndex] == false)
        {
            DFS(_RelationShip, NextIndex, _Depth + 1);

            if (isFind == true)
            {
                return;
            }
        }
    }

    isVisit[_CurIndex] = false;
}

 

DFS함수 내부 코드는 위와 같다.

먼저, 현재 깊이가 4라면 isFind를 true로 바꾼 뒤, 재귀함수를 종료해주었다.

 

깊이가 4가 아니라면, isVisit를 true로 만들어준 뒤, 갈 수 있는 경로에 대해 DFS를 재귀적으로 진행하도록 하였다.

역시나, DFS진행중에도 isFind가 true가 됐음을 발견하였다면 재귀함수를 종료하도록 해주었다.

 

마지막엔 방문체크를 해제하여 DFS가 다양한 경로에 대해 탐색할 수 있도록 해주었다.

int Answer = isFind ? 1 : 0;
std::cout << Answer;

return 0;

 

DFS가 모두 끝난 뒤엔 답을 출력해주고 종료해주었다.

 

코드 전문

더보기
#include <iostream>
#include <vector>

void Init()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
}

bool isFind = false;
std::vector<bool> isVisit;

void DFS(std::vector<std::vector<int>>& _RelationShip, int _CurIndex, int _Depth)
{
    if (_Depth == 4)
    {
        isFind = true;
        return;
    }

    isVisit[_CurIndex] = true;

    for (int i = 0; i < _RelationShip[_CurIndex].size(); i++)
    {
        int NextIndex = _RelationShip[_CurIndex][i];

        if (isVisit[NextIndex] == false)
        {
            DFS(_RelationShip, NextIndex, _Depth + 1);

            if (isFind == true)
            {
                return;
            }
        }
    }

    isVisit[_CurIndex] = false;
}

int main()
{
    Init();

    int NumOfMan = 0;
    std::cin >> NumOfMan;

    int NumOfRelationShip = 0;
    std::cin >> NumOfRelationShip;

    std::vector<std::vector<int>> RelationShip(NumOfMan);
    for (int i = 0; i < NumOfRelationShip; i++)
    {
        int First = 0;
        int Second = 0;
        std::cin >> First >> Second;

        RelationShip[First].push_back(Second);
        RelationShip[Second].push_back(First);
    }

    isVisit.resize(NumOfMan, false);

    for (int i = 0; i < NumOfMan; i++)
    {
        DFS(RelationShip, i, 0);

        if (isFind == true)
        {
            break;
        }
    }

    int Answer = isFind ? 1 : 0;
    std::cout << Answer;

    return 0;
}

쉐이더 컴파일도 했고, 이제 렌더링만 해주면 된다.

void EngineBase::Render()
{
    float clearColor[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
    Context->ClearRenderTargetView(RenderTargetView.Get(), clearColor);
    Context->ClearDepthStencilView(DepthStencilView.Get(),
        D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    Context->OMSetRenderTargets(1, RenderTargetView.GetAddressOf(), DepthStencilView.Get());
    Context->OMSetDepthStencilState(DepthStencilState.Get(), 0);
    Context->RSSetState(RasterizerState.Get());

    for (std::shared_ptr<RenderBase> Renderer : Renderers)
    {
        Renderer->Render();
    }

    SwapChain->Present(1, 0);
}

 

EngineBase에 Render함수를 만들어서 위와 같이 정의해주었다.

렌더타겟뷰를 초기화하면서 초록색으로 초기화해주었다. 그려지지 않은 부분을 좀 명확히 알기 위해서다.

뎁스스텐실 뷰도 지워주었다. 

얘네는 매 프레임 새로 사용해야하기때문에, 렌더링 시작 전에 한 번씩 지워주어야 한다.

 

다음은 렌더타겟을 세팅해주었다.

실제론 여러개의 렌더타겟을 바꿔가며 세팅하겠지만, 일단은 렌더타겟이 백버퍼 하나뿐이라고 가정하고

백버퍼의 렌더타겟으로 세팅해주었다.

뎁스스텐실스테이트와 레스터라이저스테이트도 세팅해준다음 렌더링 해주었다.

void BoxRenderer::Render()
{
    UINT Stride = sizeof(Vertex);
    UINT Offset = 0;
    
    VertexShaderData VSData = EngineBase::GetInstance().GetVertexShaderData(L"VertexTest.hlsl");
    Microsoft::WRL::ComPtr<ID3D11PixelShader> PS = EngineBase::GetInstance().GetPixelShaderData(L"PixelTest.hlsl");
    
    EngineBase::GetInstance().GetContext()->IASetVertexBuffers(0, 1, VertexBuffer.GetAddressOf(), &Stride, &Offset);
    EngineBase::GetInstance().GetContext()->IASetIndexBuffer(IndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
    EngineBase::GetInstance().GetContext()->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    EngineBase::GetInstance().GetContext()->VSSetShader(VSData.VertexShader.Get(), 0, 0);
    EngineBase::GetInstance().GetContext()->IASetInputLayout(VSData.InputLayout.Get());
    EngineBase::GetInstance().GetContext()->VSSetConstantBuffers(0, 1, ConstantBuffer.GetAddressOf());
    EngineBase::GetInstance().GetContext()->PSSetShader(PS.Get(), 0, 0);
    
    UINT IndexCount = Indices.size();
    EngineBase::GetInstance().GetContext()->DrawIndexed(IndexCount, 0, 0);
}

 

다음은 렌더러의 Render함수이다.

먼저, EngineBase가 가지고 있는 자료구조에서 쉐이더를 찾아온 뒤, 해당 쉐이더로 세팅해주었다.

 

원래는 렌더러마다 쉐이더의 이름을 저장한 뒤, 그 이름에 맞는 쉐이더를 탐색하게 할 생각이었으나 아직은 테스트중이라 일단 리터럴로 쉐이더 이름을 대입하였다.

 

쉐이더와 함께 인풋 레이아웃, 버텍스버퍼, 인덱스버퍼, 상수버퍼도 세팅을 해준 뒤, DrawIndexed를 호출해주면 끝이다.

 

 처음엔 화면에 계속 아무것도 안떠서 왜안뜨지..하고 고민하다가 비주얼 스튜디오의 그래픽 디버깅을 사용해봤는데, 프레임 캡쳐가 안되는 걸 보고 설마...스왑체인의 present를 호출안해줬나? 하고 봤더니 역시나 호출을 안하고 있었고, 호출을 해주었더니 잘 작동이 되었다.

 

아래는 결과이다.

 

아직 정육면체 하나 띄우는게 고작이지만, 이제 시작이다!

여기다 많은 걸 그리고 배워보자!

쉐이더 파일의 경우, 동일한 쉐이더가 여러번 컴파일될 필요가 없기 때문에 EngineBase에 쉐이더 파일을 따로 저장하도록 하였다.

 

그리고 쉐이더를 생성할 때, EngineBase에서 해당 쉐이더가 이미 존재하는지를 판단한 뒤 없을때에만 새로 생성하고 컴파일하도록 하였다.

 

struct VertexShaderData
{
    Microsoft::WRL::ComPtr<ID3D11VertexShader> VertexShader;
    Microsoft::WRL::ComPtr<ID3D11InputLayout> InputLayout;
};

 

이렇게, 버텍스쉐이더와 인풋 레이아웃을 함께 담는 구조체를 하나 선언하였다.

std::unordered_map<const std::wstring, VertexShaderData> VertexShaders;

 

EngineBase내부엔 위와 같은 자료구조를 선언하여,  버텍스 쉐이더 데이터를 저장하도록 하였다.

key는 쉐이더 파일의 이름이다.

 

BOOL EngineBase::CreateVertexShader(const std::wstring& _ShaderFileName, std::vector<D3D11_INPUT_ELEMENT_DESC> _InputElement)
{
    if (VertexShaders.find(_ShaderFileName) != VertexShaders.end())
    {
        std::cout << "Don't try to Create existed VertexShader" << std::endl;
        return TRUE;
    }

    Microsoft::WRL::ComPtr<ID3DBlob> ShaderBlob;
    Microsoft::WRL::ComPtr<ID3DBlob> ErrorBlob;

    HRESULT Result =
        D3DCompileFromFile(_ShaderFileName.c_str(), 0, 0, "main", "vs_5_0", 0, 0, &ShaderBlob, &ErrorBlob);

    if (Result != S_OK) 
    {
        if ((Result & D3D11_ERROR_FILE_NOT_FOUND) != 0) 
        {
            std::cout << "File not found." << std::endl;
        }

        if (ErrorBlob)
        {
            std::cout << "Shader compile error\n" << (char*)ErrorBlob->GetBufferPointer() << std::endl;
        }

        return FALSE;
    }

    Microsoft::WRL::ComPtr<ID3D11VertexShader> NewVertextShader;

    Result = 
        EngineBase::GetInstance().GetDevice()->CreateVertexShader(ShaderBlob->GetBufferPointer(), ShaderBlob->GetBufferSize(), NULL,
        &NewVertextShader);

    if (Result != S_OK)
    {
        std::cout << "CreateVertexShader() failed" << std::endl;
        return FALSE;
    }

    Microsoft::WRL::ComPtr<ID3D11InputLayout> _InputLayOut;
    if (!CreateInputLayOut(_InputElement, _InputLayOut, ShaderBlob))
    {
        return FALSE;
    }

    VertexShaders.insert({ _ShaderFileName, {NewVertextShader, _InputLayOut} });

    return TRUE;
}

 

먼저, 지금 만들고자 하는 쉐이더가 이미 생성되어 있다면 따로 만들지 않도록 해주었다.

이후, 입력된 파일 이름을 기반으로 쉐이더 컴파일을 실행하였고 파일이 있는지 없는지, 혹은 다른 오류로 컴파일이 실패하였는지 확인 과정을 거쳐주었다.

 

다음은 컴파일된 쉐이더를 통해 버텍스 쉐이더를 생성해주었다.

버텍스 쉐이더를 만들었으면, 인풋 레이아웃도 만들어야 하기 때문에, 인풋 레이아웃도 만들어주었다.

 

마지막으로, VertexShaders 자료구조에 데이터를 삽입해주었다.

BOOL EngineBase::CreateInputLayOut(std::vector<D3D11_INPUT_ELEMENT_DESC> _InputElement, Microsoft::WRL::ComPtr<ID3D11InputLayout> _InputLayOut, Microsoft::WRL::ComPtr<ID3DBlob> _ShaderBlob)
{
    HRESULT Result = 
    EngineBase::GetInstance().GetDevice()->CreateInputLayout(_InputElement.data(), UINT(_InputElement.size()),
        _ShaderBlob->GetBufferPointer(), _ShaderBlob->GetBufferSize(),
        &_InputLayOut);

    if (Result != S_OK)
    {
        std::cout << "CreateInputLayOut() failed" << std::endl;
        return FALSE;
    }

    return TRUE;
}

인풋 레이아웃 생성 함수는 위와 같다. 간단하다.

 

std::unordered_map<const std::wstring, Microsoft::WRL::ComPtr<ID3D11PixelShader>> PixelShaders;

픽셀 쉐이더에 대해서도 자료구조를 생성해주었다.

 

BOOL EngineBase::CreatePixelShader(const std::wstring& _ShaderFileName)
{
    Microsoft::WRL::ComPtr<ID3DBlob> ShaderBlob;
    Microsoft::WRL::ComPtr<ID3DBlob> ErrorBlob;

    HRESULT Result =
        D3DCompileFromFile(_ShaderFileName.c_str(), 0, 0, "main", "ps_5_0", 0, 0, &ShaderBlob, &ErrorBlob);
    
    if (Result != S_OK)
    {
        if ((Result & D3D11_ERROR_FILE_NOT_FOUND) != 0)
        {
            std::cout << "File not found." << std::endl;
        }

        if (ErrorBlob)
        {
            std::cout << "Shader compile error\n" << (char*)ErrorBlob->GetBufferPointer() << std::endl;
        }

        return FALSE;
    }

    Microsoft::WRL::ComPtr<ID3D11PixelShader> NewPixelShader;

    Result = 
    EngineBase::GetInstance().GetDevice()->CreatePixelShader(ShaderBlob->GetBufferPointer(), ShaderBlob->GetBufferSize(), NULL,
        &NewPixelShader);

    if (Result != S_OK)
    {
        std::cout << "CreatePixelShader() failed" << std::endl;
        return FALSE;
    }

    PixelShaders.insert({ _ShaderFileName, NewPixelShader });

    return TRUE;
}

 

픽셀 쉐이더도 버텍스 쉐이더와 동일하게 만들어주었다.

이제, 조금만 더 하면 화면에 정육면체를 띄울 수 있을 것 같다...!

버텍스 버퍼, 인덱스 버퍼와 동일한 과성으로 상수버퍼도 추가해주었다.

글이 나뉘는게 좀 불편하긴 한데, 글 제목이 너무 길어지는 것도 거슬려서 따로 작성하였다.

 

template <typename DataType>
void CreateConstantBuffer(DataType& _Data)
{
    D3D11_BUFFER_DESC CBDesc;
    CBDesc.ByteWidth = sizeof(DataType);
    CBDesc.Usage = D3D11_USAGE_DYNAMIC;
    CBDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    CBDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    CBDesc.MiscFlags = 0;
    CBDesc.StructureByteStride = 0;

    D3D11_SUBRESOURCE_DATA InitData;
    InitData.pSysMem = &_Data;
    InitData.SysMemPitch = 0;
    InitData.SysMemSlicePitch = 0;

    HRESULT Result =
        EngineBase::GetInstance().GetDevice()->CreateBuffer(&CBDesc, &InitData, ConstantBuffer.GetAddressOf());

    if (Result != S_OK)
    {
        std::cout << Name << " :" << "CreateConstantBuffer() failed." << std::hex << "\nResult : " << Result << std::endl;
    };
}

 

동일한 방식에 옵션만 다르게 하여 상수버퍼도 만들어주었다.

상수버퍼는 일단 Transform에 대해서만 사용할 예정이지만, 추후 어떤 타입이 추가될지 알 수 없기 때문에

템플릿을 이용하여 여러 자료형에 대한 가능성을 열어놓은 채로 정의하였다.

 

일단 실행은 잘 되는데, 화면에 보이지를 않으니 제대로 되고 있는건지 잘 모르겠다.

빨리 쉐이더까지 추가를 해보아야 할 듯 하다.

저번에, EngineBase를 싱글톤 패턴으로 바꾸고 나니, 메모리 누수가 감지되었다.

아무래도 소멸자의 호출 시기가 애매해서 그런 것 같았다.

 

실제로, 메모리 해제 함수를 만든다음 프로그램이 종료되기 전에 명시적으로 메모리를 해제해주니 메모리 누수가 사라졌음을 확인할 수 있었다.

 

하지만, 이렇게 메모리 해제를 직접 호출해야 하는 상황이 그렇게 좋은 방식으로 보이지는 않았고, 그냥 마이어스 싱글톤 형식으로 수정하였다.

public:
    static EngineBase& GetInstance()
    {
        static EngineBase Instance;
        return Instance;
    }

이렇게, 지역 스태틱을 선언하고 참조자를 반환하는 형식으로 구현하였고, 동적으로 메모리를 할당하지 않았기 때문에 당연히 누수도 사라졌음을 확인할 수 있었다.

렌더링 파이프라인을 설정하기 전에, 렌더러의 간단한 구조를 먼저 잡고 가는게 좋을 것 같아서 렌더러를 일단 구성해보았다.

#pragma once
#include <memory>

class RenderBase
{

public:
    RenderBase();
    ~RenderBase();

    RenderBase(const RenderBase& _Other) = delete;
    RenderBase(RenderBase&& _Other) noexcept = delete;
    RenderBase& operator=(const RenderBase& _Other) = delete;
    RenderBase& operator=(RenderBase&& _Other) noexcept = delete;

public:
    virtual void Init() = 0;
    virtual void Render() = 0;

protected:
    std::vector<Vertex> Vertices;
    std::vector<uint16_t> Indices;

    Microsoft::WRL::ComPtr<ID3D11Buffer> VertexBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer> IndexBuffer;
    Microsoft::WRL::ComPtr<ID3D11Buffer> ConstantBuffer;
private:

};

 

일단, 모든 렌더러들은 RenderBase를 상속받도록 설정하였다.

EngineBase에서는 RenderBase으로 렌더러들을 저장한 뒤, 렌더링 할 때 Render함수를 실행할 것이기 때문에, Render함수를 순수가상함수로 만들어 하위 클래스에서 반드시 구현하도록 설정하였다.

 

Init도 마찬가지로, 렌더러가 처음 생성될 때 설정해야 하는 것들을 반드시 정의하도록 순수가상함수로 만들어두었다.

그 외에도, 렌더링에 사용될 버텍스 관련 변수들을 선언해주었다.

 

public:
    template <typename T>
    static std::shared_ptr<T> CreateRenderer()
    {
        std::shared_ptr<RenderBase> NewRenderer = std::make_shared<T>();
        NewRenderer->Init();
        Renderers.insert(NewRenderer);

        return std::dynamic_pointer_cast<T>(NewRenderer);
    }

 

렌더러의 생성은 EngineBase 클래스에 static함수로 선언해두었다.

템플릿 함수로 선언하여, RenderBase를 상속받은 클래스라면 어떤 클래스든 생성할 수 있도록 구성하였고

EngineBase가 소유하고 있는 RenderBase를 담는 자료구조인 Renderers에 생성된 Renderer를 insert해주었다.

private:
    std::list<std::shared_ptr<RenderBase>> Renderers;

 

렌더러가 몇 개가 될 지 예측이 불가능하기 때문에, 수시로 Reserve를 해주어야 하는 vector대신 list를 사용하였다.

이후, 렌더링 기능이 추가되면 위의 list의 원소를 순회하면서 Render함수를 호출해줄 것이다.

 

이제, 버텍스 버퍼와 인덱스 버퍼 등 렌더링에 필요한 것들을 추가해보자.

테스트용으로 박스 렌더러 하나를 추가해볼것이다.

#pragma once
#include "RenderBase.h"

class BoxRenderer : public RenderBase
{

public:   
    BoxRenderer();
    ~BoxRenderer();

    BoxRenderer(const BoxRenderer& _Other) = delete;
    BoxRenderer(BoxRenderer&& _Other) noexcept = delete;
    BoxRenderer& operator=(const BoxRenderer& _Other) = delete;
    BoxRenderer& operator=(BoxRenderer&& _Other) noexcept = delete;

public:
    virtual void Render() override{}
    virtual void Init() override{}

protected:

private:
    void CreateVertexAndIndex();
};

 

이렇게 RenderBase 클래스를 상속받았으며, Render함수와 Init함수를 오버라이딩 해주었다.

멤버함수로 Vertex와 Index를 만드는 함수도 만들어주었다.

 

함수 내부는 대강 아래와 같은데, 그냥 노가다이다.

더보기
void BoxRenderer::CreateVertexAndIndex()
{
    std::vector<DirectX::SimpleMath::Vector3> Positions;
    Positions.reserve(24);

    std::vector<DirectX::SimpleMath::Vector3> Colors;
    Colors.reserve(24);

    std::vector<DirectX::SimpleMath::Vector3> Normals;
    Normals.reserve(24);

    //윗면
    Positions.push_back({ -1.0f, 1.0f, -1.0f });
    Positions.push_back({ -1.0f, 1.0f, 1.0f });
    Positions.push_back({ 1.0f, 1.0f, 1.0f });
    Positions.push_back({ 1.0f, 1.0f, -1.0f });
    
    Colors.push_back({1.0f, 0.0f, 0.0f});
    Colors.push_back({1.0f, 0.0f, 0.0f});
    Colors.push_back({1.0f, 0.0f, 0.0f});
    Colors.push_back({1.0f, 0.0f, 0.0f});
   
    Normals.push_back({0.0f, 1.0f, 0.0f});
    Normals.push_back({0.0f, 1.0f, 0.0f});
    Normals.push_back({0.0f, 1.0f, 0.0f});
    Normals.push_back({ 0.0f, 1.0f, 0.0f});

    //아랫면
    Positions.push_back({ 1.0f, -1.0f, 1.0f });
    Positions.push_back({ 1.0f, -1.0f, -1.0f });
    Positions.push_back({ -1.0f, -1.0f, -1.0f });
    Positions.push_back({ -1.0f, -1.0f, 1.0f });

    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });

    Normals.push_back({ 0.0f, -1.0f, 0.0f });
    Normals.push_back({ 0.0f, -1.0f, 0.0f });
    Normals.push_back({ 0.0f, -1.0f, 0.0f });
    Normals.push_back({ 0.0f, -1.0f, 0.0f });

    //왼쪽
    Positions.push_back({ -1.0f, -1.0f, -1.0f });
    Positions.push_back({ -1.0f, -1.0f, 1.0f });
    Positions.push_back({ -1.0f, 1.0f, 1.0f });
    Positions.push_back({ -1.0f, 1.0f, -1.0f });

    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });

    Normals.push_back({ -1.0f, 0.0f, 0.0f });
    Normals.push_back({ -1.0f, 0.0f, 0.0f });
    Normals.push_back({ -1.0f, 0.0f, 0.0f });
    Normals.push_back({ -1.0f, 0.0f, 0.0f });

    //오른쪽
    Positions.push_back({ 1.0f, 1.0f, -1.0f });
    Positions.push_back({ 1.0f, 1.0f, 1.0f });
    Positions.push_back({ 1.0f, -1.0f, 1.0f });
    Positions.push_back({ 1.0f, -1.0f, -1.0f });

    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });

    Normals.push_back({ 1.0f, 0.0f, 0.0f });
    Normals.push_back({ 1.0f, 0.0f, 0.0f });
    Normals.push_back({ 1.0f, 0.0f, 0.0f });
    Normals.push_back({ 1.0f, 0.0f, 0.0f });

    //앞쪽
    Positions.push_back({ -1.0f, 1.0f, 1.0f });
    Positions.push_back({ -1.0f, -1.0f, 1.0f });
    Positions.push_back({ 1.0f, -1.0f, 1.0f });
    Positions.push_back({ 1.0f, 1.0f, 1.0f });

    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });

    Normals.push_back({ 0.0f, 0.0f, 1.0f });
    Normals.push_back({ 0.0f, 0.0f, 1.0f });
    Normals.push_back({ 0.0f, 0.0f, 1.0f });
    Normals.push_back({ 0.0f, 0.0f, 1.0f });

    //뒷쪽
    Positions.push_back({ -1.0f, -1.0f, -1.0f });
    Positions.push_back({ -1.0f, 1.0f, -1.0f });
    Positions.push_back({ 1.0f, 1.0f, -1.0f });
    Positions.push_back({ 1.0f, -1.0f, -1.0f });

    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });
    Colors.push_back({ 1.0f, 0.0f, 0.0f });

    Normals.push_back({ 0.0f, 0.0f, -1.0f });
    Normals.push_back({ 0.0f, 0.0f, -1.0f });
    Normals.push_back({ 0.0f, 0.0f, -1.0f });
    Normals.push_back({ 0.0f, 0.0f, -1.0f });


    for (size_t i = 0; i < Positions.size(); i++) 
    {
        Vertex NewVertex;
        NewVertex.Position = Positions[i];
        NewVertex.Color = Colors[i];
        NewVertex.Normal = Normals[i];

        Vertices.push_back(NewVertex);
    }

    Indices = { 0, 1, 2,
                0, 2, 3,
                4, 6, 5,
                4, 7, 6,
                8, 9, 10,
                8, 10, 11,
                12, 13, 14,
                12, 14, 15,
                16, 17, 18,
                16, 18, 19,
                20, 21, 22,
                20, 22, 23 };
}

 

이렇게, 버텍스에 관한 정보를 모두 Vertices와 Indices에 담아주었다면, 이 정보를 기반으로 버퍼를 만들어야 한다.

버퍼를 만드는 함수는 RenderBase에 정의해두었다.

 

그런데, RenderBase에서 버텍스 버퍼를 만들려면 Device가 필요했는데, EngineBase의 Device를 어떻게 참조해야하나 고민하다가 그냥 EngineBase 클래스를 전역변수로 바꿔서 생성해주었다.

 

private:
    EngineBase();

    ~EngineBase()
    {
    	if (Instance != nullptr)
    	{
    	    delete (Instance);
    	}
    }

    static EngineBase* Instance;

public:
    static EngineBase* GetInstance()
    {
        if (Instance == nullptr)
        {
            Instance = new EngineBase();
        }

        return Instance;
    }

 

이렇게, Enginebase를 싱글톤 방식으로 구성한 뒤, 전역변수로 선언해주었다.

이후, EngineBase내부에 GetDevice함수를 추가해주었다.

void RenderBase::CreateVertexBuffer()
{
    D3D11_BUFFER_DESC bufferDesc;
    ZeroMemory(&bufferDesc, sizeof(bufferDesc));

    bufferDesc.Usage = D3D11_USAGE_IMMUTABLE; 
    bufferDesc.ByteWidth = UINT(sizeof(Vertex) * Vertices.size());
    bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bufferDesc.CPUAccessFlags = 0; 
    bufferDesc.StructureByteStride = sizeof(Vertex);

    D3D11_SUBRESOURCE_DATA vertexBufferData = { 0, }; 
    vertexBufferData.pSysMem = Vertices.data();
    vertexBufferData.SysMemPitch = 0;
    vertexBufferData.SysMemSlicePitch = 0;

    const HRESULT Result =
        EngineBase::GetInstance()->GetDevice()->CreateBuffer(&bufferDesc, &vertexBufferData, VertexBuffer.GetAddressOf());

    if (Result != S_OK) 
    {
        std::cout << "CreateVertexBuffer() failed. " << std::hex << Result << std::endl;
    };
}

 

그리고 규칙에 맞게 버텍스 버퍼를 생성해주었다.

이렇게 작성하고 보니, 어떤 렌더러에서 버퍼 생성이 실패했는지 가시적으로 확인하기 위해 이름을 부여해주는 것이 좋겠다 싶었고, RenderBase에 이름을 추가해주었다.

BoxRenderer::BoxRenderer()
{
    Name = "BOX";
}

 

그리고 이렇게 생성자에서 이름을 설정해주고, 위의 버텍스 버퍼 생성 코드의 마지막 중에 있는 출력을 아래와 같이 바꿔주었다.

if (Result != S_OK) 
{
    std::cout << Name << " :" << "CreateVertexBuffer() failed." << std::hex << "\nResult : " << Result  << std::endl;
};

 

void RenderBase::CreateIndexBuffer()
{
    D3D11_BUFFER_DESC bufferDesc = {0, };
    bufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    bufferDesc.ByteWidth = UINT(sizeof(uint16_t) * Indices.size());
    bufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    bufferDesc.CPUAccessFlags = 0; 
    bufferDesc.StructureByteStride = sizeof(uint16_t);

    D3D11_SUBRESOURCE_DATA indexBufferData = { 0 };
    indexBufferData.pSysMem = Indices.data();
    indexBufferData.SysMemPitch = 0;
    indexBufferData.SysMemSlicePitch = 0;

    HRESULT Result = 
        EngineBase::GetInstance()->GetDevice()->CreateBuffer(&bufferDesc, &indexBufferData, IndexBuffer.GetAddressOf());

    if (Result != S_OK)
    {
        std::cout << Name << " :" << "CreateIndexBuffer() failed." << std::hex << "\nResult : " << Result << std::endl;
    };
}

 

인덱스 버퍼도 만들어주었다.

 

이제, 상수버퍼도 만들고 쉐이더도 연결해야 하는데, 일단 밥먹고 나서 진행해야겠다.

 

 

주어진 수열의 부분 수열 중 같은 원소의 개수가 k개 이하인 수열중 가장 긴 수열을 구하면 된다.

 

예를 들어, 1223334444 라는 수열이 있다고 해보자.

이 수열에는 12233, 223334, 33344 등 여러 부분 수열이 존재한다.

 

각 부분 수열을 보면, 동일한 원소가 여러개 포함되어 있기도 한다.

12233의 경우엔 2와 3을 각각 2개씩 포함하고 있으며, 33344는 3을 3개, 4를 2개 포함하고 있다.

 

한 부분 수열에서 동일한 원소를 포함하고 있는 개수가 k개를 넘지 않는 건에서 가장 긴 수열을 구하면 된다.

 

주어진 수열은 1223334444이고, k는 2이라고 해보자.

12233 이라는 부분 수열은 총 5개의 길이를 지니고 있으며, 2는 2개, 3은 2개 가지고 있으므로 중복 원소의 개수가 k를 넘지 않는다.

 

하지만, 122333이 된다면, 3의 개수가 3개가 되어버리므로 k를 초과하는 부분 수열이 된다. 즉, 122333은 답이 될 수 없는 것이다. 

 

이런 조건을 고려하였을 때, 가장 긴 부분 수열의 길이를 출력하면 된다.

 

문제 풀이

 

투 포인터를 사용하면, 어렵지 않게 해결할 수 있다.

두개의 포인터를 사용하여, 수열을 하나씩 탐색하며, 중복 숫자의 개수가 k개가 넘지 않도록 관리하며 탐색해주었다.

 

입력은 1223334444 수열과, k는 2라고 가정해보자.

 

먼저 위의 배열은 투포인터가 가리키고 있는 범위 내에 숫자가 몇개 있는지를 기록하는 배열이다.

현재, 두 포인터 모두 0번 인덱스를 가리키고 있고, 1이 1개 포함되어 있으므로 1을 기록해주었다.

이제, R을 움직이며 탐색해보자.

 

R이 움직이면서 2가 범위에 포함되었으므로 2의 값도 1로 올려주었다.

 

다음 역시, R이 움직이면서 2를 2개 포함하게 되었으므로 2의 값을 2로 갱신해주었다.

조금 스킵해보겠다.

R이 이 구간까지 간다면, 수의 개수를 담은 배열은 위처럼 갱신되어있을 것이다.

이 때, 다음에서 문제가 발생한다.

3의 개수가 k를 초과하게 되어버린다.

즉, R이 이동하기 전의 L과 R의 범위가 중복 원소가 2인 부분 수열 중 하나의 경우가 된다는 것이다

그러므로, 이 때 길이를 기록해두고, L을 움직여서 다음 탐색 범위를 찾아야 한다.

현재 기록된 부분 수열의 최대 길이는 5가 될 것이다.

 

현재, 3이 3개이므로 3이 k개 이하가 될 때까지 L을 움직여보자.

 

이렇게 이동할 수 있다.

이전과 동일하게 R을 움직이며 구간을 탐색하면 된다.

 

끝까지 탐색해보면, 12233의 구간이 가장 긴 부분 수열이 될 수 있음을 알 수 있다.

 

풀이 코드

std::vector<int> NumCount(100001, 0);

int NumSize = 0;
int SameCount = 0;

std::cin >> NumSize >> SameCount;

std::vector<int> Sequence(NumSize);
for (int i = 0; i < NumSize; i++)
{
    std::cin >> Sequence[i];
}

 

먼저,  NumCount라는 배열을 선언해주었다.

현재 투 포인터가 가리키는 구간에 숫자가 몇개있는지 기록하는 배열이다.

수열에 어떤 숫자가 포함되어 있을 지 예상할 수 없으므로, 최대 범위인 100000에 맞게 resize해주었다.

 

int Left = 0;
int Right = 0;

int MaxLength = 0;

 

이후, 범위 양 끝을 가리킬 두 개의 포인터를 선언하였고, 조건에 맞는 부분 수열의 최대 길이를 저장할 MaxLength변수도 선언하였다.

 

while (Right < NumSize)
{
    int CurNum = Sequence[Right];
    NumCount[CurNum]++;

    if (NumCount[CurNum] > SameCount)
    {
        MaxLength = std::max(MaxLength, Right - Left);

        while (NumCount[CurNum] > SameCount)
        {
            int Num = Sequence[Left];
            NumCount[Num]--;
            Left++;
        }
    }

    Right++;
}

 

반복문을 돌며 위에서 설명한 것과 동일하게 진행하였다.

 

현재 Right가 가리키는 수의 NumCount를 증가시켜주었고, 만약 NumCount가 k보다 크다면, 이전 Right의 위치를 기준으로 부분 수열의 길이를 MaxLength에 갱신한 뒤, Right가 가리키는 숫자의 NumCount가 k이하가 될 때 까지 Left를 우측으로 당겨주었다. 

 

MaxLength = std::max(MaxLength, NumSize - Left);

std::cout << MaxLength;
return 0;

 

반복문이 끝난 후, 마지막 범위는 기록되지 않았으므로 MaxLength를 (NumSize - Left) 와 비교해서 더 큰 값으로 한 번 더 갱신해주었다.

 

마지막으로 MaxLength를 출력해주면 끝이다.

 

코드 전문

더보기
#include <iostream>
#include <vector>

void Init()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
}

int main()
{
    Init();
    std::vector<int> NumCount(100001, 0);

    int NumSize = 0;
    int SameCount = 0;

    std::cin >> NumSize >> SameCount;

    std::vector<int> Sequence(NumSize);
    for (int i = 0; i < NumSize; i++)
    {
        std::cin >> Sequence[i];
    }

    int Left = 0;
    int Right = 0;

    int MaxLength = 0;

    while (Right < NumSize)
    {
        int CurNum = Sequence[Right];
        NumCount[CurNum]++;

        if (NumCount[CurNum] > SameCount)
        {
            MaxLength = std::max(MaxLength, Right - Left);

            while (NumCount[CurNum] > SameCount)
            {
                int Num = Sequence[Left];
                NumCount[Num]--;
                Left++;
            }
        }

        Right++;
    }

    MaxLength = std::max(MaxLength, NumSize - Left);

    std::cout << MaxLength;
    return 0;
}

 

'코딩테스트 문제 풀이 (C++)' 카테고리의 다른 글

백준 13903 - 과제 (C++)  (0) 2024.05.04
백준 13023 - ABCDE (C++)  (0) 2024.05.04
백준 20310 - 타노스 (C++)  (0) 2024.04.30
백준 1522 - 문자열 교환 (C++)  (0) 2024.04.29
백준 5464 - 주차장 (C++)  (1) 2024.04.28

+ Recent posts