빌드 과정은 크게 전처리기, 컴파일, 어셈블러, 링커 순으로 실행됩니다.

 

 

[전처리기]

 

유저가 작성한 코드를 저수준 언어로 변환하기 위한 준비를 실시합니다. 주석, 공백 등 불필요한 요소를 제거하고 매크로 구문을 치환하며, 헤더 파일의 코드 전체를 소스파일 내에 추가하게 됩니다.

 

 

[컴파일]

 

전처리 과정을 거친 코드를 저수준의 어셈블리어로 번역하는 동시에 문법상의 오류를 검출하기도 합니다.

 

 

[어셈블러]

 

어셈블러 과정에서는 어셈블리어를 0과 1로 이루어진 바이너리 코드로 변환합니다. 변환된 바이너리 코드는 여러 개의 오브젝트 파일(.obj)로 저장됩니다.

 

 

[링커]

 

위에서 여러 개로 저장된 오브젝트 파일을 해당 단계에서 하나의 프로그램에서 작동하도록 연결해줍니다. 이 과정에서 정적 라이브러리가 프로그램과 함께 묶이게 됩니다. 하나로 묶인 프로그램은 exe 파일로 저장되며 빌드가 완료됩니다.

'C++' 카테고리의 다른 글

[C++] 완벽한 전달(Perfact Forwarding)과 std::forward  (0) 2025.05.16
[C++] 이동 생성자와 std::move()  (0) 2025.05.16
[C++] 좌측값과 우측값(lvalue and rvalue)  (0) 2025.05.15
[C++] 복사 생략(Copy Elision)  (0) 2025.05.15
[C++] union  (0) 2025.05.14

[문제]

https://school.programmers.co.kr/learn/courses/30/lessons/67257

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

[풀이]

 

수식이 여러 개 주어지면 어려울 수 있겠지만, 지문에서는 최대 [ *, +, - ]만 주어졌기 때문에 충분히 next_permutation을 활용할 수 있을 것이라 판단했습니다. 이를 위해 처음 받는 expression을 파싱해줍니다.

 

1. Numbers : 파싱된 숫자들

2. Formulas : 파싱된 연산자들

3. FormulaSet : 연산자가 어떤 것들이 있는지

 

3번을 따로 사용한 이유는 중복을 제거하여 expression 내에 무슨 종류의 연산자가 존재하는지 판단하기 위함입니다. 이제 이 3종류의 컨테이너에 파싱된 값들을 넣어주고, next_permutation을 실행합니다.

 

순회 도중 중간에 요소를 삭제하기 위하여 for문보다는 while문을 활용했으며, 순회를 실시하여 현재 지정된 Operator와 Fomula가 동일하다면, Formula에 해당하는 index와 index+1의 값을 서로 연산해주고 erase를 실시하여 컨테이너를 갱신하는 작업을 실시했습니다. 이렇게 계속 수를 줄여나가다보면 최종적으로 현재 순열에 의해 조합된 연산자 순서대로 값을 알아낼 수 있게 됩니다.

#include <string>
#include <vector>
#include <unordered_set>
#include <algorithm>
#include <iostream>

using namespace std;

long long solution(string expression)
{
    long long answer = 0;

    vector<long long> Numbers;
    vector<char> Formulas;
    unordered_set<char> FormulaSet;

    string CurNumber;
    for (size_t i = 0; i < expression.size(); ++i)
    {
        char c = expression[i];
        if ('0' <= c && c <= '9')
        {
            CurNumber += c;
        }
        else
        {
            Numbers.push_back(stoll(CurNumber));
            CurNumber.clear();

            Formulas.push_back(c);
            FormulaSet.insert(c);
        }
    }
    // 마지막 숫자
    if (!CurNumber.empty())
    {
        Numbers.push_back(stoll(CurNumber));
    }

    vector<char> Arr;
    Arr.reserve(FormulaSet.size());
    for (char Operator : FormulaSet)
    {
        Arr.push_back(Operator);
    }
    sort(Arr.begin(), Arr.end());

    // 3) 모든 우선순위 순열 시도
    do
    {
        vector<long long> NTemp = Numbers;
        vector<char> FTemp = Formulas;

        for (char Operator : Arr)
        {
            size_t j = 0;
            while (j < FTemp.size())
            {
                if (FTemp[j] == Operator)
                {
                    long long L = NTemp[j];
                    long long R = NTemp[j + 1];
                    long long Result = 0;

                    if (Operator == '*')      Result = L * R;
                    else if (Operator == '+') Result = L + R;
                    else                Result = L - R;

                    NTemp[j] = Result;
                    NTemp.erase(NTemp.begin() + (j + 1));
                    FTemp.erase(FTemp.begin() + j);
                }
                else
                {
                    ++j;
                }
            }
        }

        long long value = llabs(NTemp[0]);
        if (value > answer) answer = value;

    } while (next_permutation(Arr.begin(), Arr.end()));

    return answer;
}

[문제]

https://school.programmers.co.kr/learn/courses/30/lessons/77485

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

[풀이]

 

딱히 어떤 알고리즘이 필요한 문제는 아닌 것 같아 algorithm의 rotate() 함수를 활용하여 풀었습니다. 구역을 총 네 개로 나눈 뒤, 각 구역의 값을 vector로 받아와 rotate()를 실시합니다. 저의 풀이에서는 1, 2 단계의 rotate()의 경우 오른쪽으로 회전, 3, 4 단계의 rotate()의 경우 왼쪽으로 회전시키고 값을 저장(CashMap)해둔 뒤, 최종적으로 원본 데이터에 적용(Map)하는 방식으로 진행했습니다. 추가로 각 요소를 Temp vector에 담을 때마다 min()을 활용하여 보다 낮은 값을 각 단계마다 실시하여 저장해두고 마지막에 answer에 담아주면 됩니다.

#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

// 주어진 쿼리로 회전시키고(y, x, y, x), 회전시킨 수중에 가장 낮은 수를 answer에 입력해둔 뒤 리턴
vector<int> solution(int rows, int columns, vector<vector<int>> queries)
{
    vector<int> answer;
    vector<vector<int>> Map(rows);
    int Count = 1;
    for (int y = 0; y < rows; y++)
    {
        for (int x = 0; x < columns; x++)
        {
            Map[y].push_back(Count++);
        }
    }
    vector<vector<int>> CashMap = Map;

    int Size = queries.size();
    int QueryIndex = 0;

    while (Size--)
    {
        int Y1 = queries[QueryIndex][0] - 1;
        int X1 = queries[QueryIndex][1] - 1;
        int Y2 = queries[QueryIndex][2] - 1;
        int X2 = queries[QueryIndex][3] - 1;
        ++QueryIndex;
        int Min = INT32_MAX;

        // [1]
        vector<int> Temp1;
        int Index = 1;
        {
            // Map[Y1][X1]; ~ Map[Y1][X2];
            // 맨 처음 버림
            for (int i = X1; i <= X2; ++i)
            {
                Min = min(Min, Map[Y1][i]);
                Temp1.push_back(Map[Y1][i]);
            }

            rotate(Temp1.begin(), Temp1.end() - 1, Temp1.end());

            for (int i = X1 + 1; i <= X2; ++i)
            {
                CashMap[Y1][i] = Temp1[Index++];
            }
        }

        // [2]
        vector<int> Temp2;
        Index = 1;
        {
            // Map[Y1][X2]; ~ Map[Y2][X2];
            // 맨 처음 버림
            for (int i = Y1; i <= Y2; ++i)
            {
                Min = min(Min, Map[i][X2]);
                Temp2.push_back(Map[i][X2]);
            }

            rotate(Temp2.begin(), Temp2.end() - 1, Temp2.end());

            for (int i = Y1 + 1; i <= Y2; ++i)
            {
                CashMap[i][X2] = Temp2[Index++];
            }
        }

        // [3]
        vector<int> Temp3;
        Index = 0;
        {
            // Map[Y2][X2]; ~ Map[Y2][X1];
            // 맨 마지막 버림
            for (int i = X1; i <= X2; ++i)
            {
                Min = min(Min, Map[Y2][i]);
                Temp3.push_back(Map[Y2][i]);
            }

            rotate(Temp3.begin(), Temp3.begin() + 1, Temp3.end());

            for (int i = X1; i <= X2 - 1; ++i)
            {
                CashMap[Y2][i] = Temp3[Index++];
            }
        }

        // [4]
        vector<int> Temp4;
        Index = 0;
        {
            // Map[Y2][X1]; ~ Map[Y1][X1];
            // 맨 마지막 버림
            for (int i = Y1; i <= Y2; ++i)
            {
                Min = min(Min, Map[i][X1]);
                Temp4.push_back(Map[i][X1]);
            }

            rotate(Temp4.begin(), Temp4.begin() + 1, Temp4.end());

            for (int i = Y1; i <= Y2 - 1; ++i)
            {
                CashMap[i][X1] = Temp4[Index++];
            }
        }

        answer.push_back(Min);
        Map = CashMap;

        //for (size_t i = 0; i < Map.size(); i++)
        //{
        //    for (size_t j = 0; j < Map[i].size(); j++)
        //    {
        //        cout << Map[i][j] << ' ';
        //    }
        //    cout << endl;
        //}
    }

    return answer;
}

[문제]

https://school.programmers.co.kr/learn/courses/30/lessons/12936

 

코딩테스트 연습 - 줄 서는 방법

알고리즘 문제 연습 카카오톡 친구해요! 프로그래머스 교육 카카오 채널을 만들었어요. 여기를 눌러, 친구 추가를 해주세요. 신규 교육 과정 소식은 물론 다양한 이벤트 소식을 가장 먼저 알려

school.programmers.co.kr

 

[풀이]

 

단순하게 C++ algorithm의 next_permutation으로는 문제를 풀 수 없습니다. 조건 중 n으로 입력되는 값이 20 이하인데, 만약 n이 20이 들어올 경우 조합의 갯수는 20!(2,432,902,008,176,640,000)이 됩니다.

 

고로 문제의 접근 방법을 조금 생각해봐야 합니다. 먼저 간단하게 { 1, 2, 3 }으로 조합을 만들어봅니다.

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

 

다음은 { 1, 2, 3, 4 }로 조합을 만들어봅니다.

1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1

 

조금 살펴보면, 특이한 점을 확인할 수 있습니다.

 

- 모든 조합의 경우의 수: n!

- 첫 번째 자리가 변하는 경우의 수 : (n-1)!

- 두 번째 자리가 변하는 경우의 수 : (n-2)!

- ...

 

결국 이 방식으로 파고 들어가면, k번째에 생성되는 조합은 어떤 경우를 갖는지 알아낼 수 있습니다. { 1, 2, 3 } 수열에서 5번째로 오는 값을 찾는 과정을 아래의 함수로 살펴보겠습니다.

long long factorial(int _Num)
{
    long long Result = 1;
    for (int i = 1; i <= _Num; ++i)
    {
        Result = Result * i;
    }
    return Result;
}

// ...
--k;
for (int i = 0; i < n; ++i)
{
    long long Value = factorial(n - 1 - i);
    long long Index = k / Value;
    k %= Value;

    if (Index >= Numbers.size())
    {
        Index = Numbers.size() - 1;
    }

    answer.push_back(Numbers[Index]);
    Numbers.erase(Numbers.begin() + Index);
}

 

1. --k : 인덱스는 0부터 계산하기 때문에, 1을 빼서 활용

2. n = 3일 때, 가능한 순열 수는 3! = 6 입니다.

3. i = 0 일 경우

- Value는 (3-1-0)! = 2! = 2

- Index는 4 / 2 = 2

- k %= Value는 0

- 이로써 3이 선택되고, 1. 2가 남습니다.

4. i = 1 일 경우

- Value는 (3-1-1)! = 1! = 1

- Index는 0 / 1 = 0

- k %= Value는 0

- 이로써 1이 선택되고, 2가 남습니다.

4. i = 2 일 경우

- Value는 (3-1-2)! = 0! = 1

- Index는 0 / 1 = 0

- k %= Value는 0

- 이로써 2가 선택되고, 종료됩니다.

 

결론은 전체 경우의 수 중 5번째의 경우로 3을 선택하고, 그 안에서 다시 첫 번째로 와야하는 경우로 1을 선택하고, 이 과정을 반복하는 것입니다.

#include <string>
#include <vector>

using namespace std;

long long factorial(int _Num)
{
    long long Result = 1;
    for (int i = 1; i <= _Num; ++i)
    {
        Result = Result * i;
    }
    return Result;
}

vector<int> solution(int n, long long k)
{
    vector<int> answer;
    vector<long long> Numbers;
    for (int i = 1; i <= n; ++i)
    {
        Numbers.push_back(i);
    }

    --k;
    for (int i = 0; i < n; ++i)
    {
        long long Value = factorial(n - 1 - i);
        long long Index = k / Value;
        k %= Value;

        if (Index >= Numbers.size())
        {
            Index = Numbers.size() - 1;
        }

        answer.push_back(Numbers[Index]);
        Numbers.erase(Numbers.begin() + Index);
    }

    return answer;
}

[문제]

 

https://school.programmers.co.kr/learn/courses/30/lessons/154540

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

[풀이]

 

크게 특별한 조건이 없는 그래프 탐색 문제입니다. 우선 방문 설정을 위한 Visited 배열을 하나 만들어주고, 방문했거나 X인 부분은 N, 방문 가능한 곳은 Y로 기록해둡니다.

 

이후 배열을 순회하면서  Y인 부분이 있다면 해당 지점을 기준으로 BFS()를 실시합니다. BFS를 모두 돌면 연결된 노드들에 대해 Add 값이 산출됩니다. 물론 방문한 노드들은 N으로 기록해주기 때문에 이후 배열 순회에서 해당 지점을 기준으로 BFS()가 실시되지 않을 것입니다.

 

이 과정을 배열의 모든 요소에 대해 실시하면 무인도(연결된 노드들) 갯수 만큼 답이 나옵니다. 마지막에 오름차순으로 sort()후 리턴해줍니다. 물론 아무것도 방문할 수 없는 상태라면 answer가 empty() 상태일 것입니다. 이 때만 -1을 push_back() 하여 리턴해줍니다.

#include <string>
#include <vector>
#include <queue>
#include <algorithm>

int SearchY[4] = { -1, 0, 1, 0 };
int SearchX[4] = { 0, 1, 0, -1 };

using namespace std;
vector<string> Visited;

void BFS(vector<string>& maps, int _StartY, int _StartX, int& _Add)
{
    queue<pair<int, int>> Que;
    Que.push(make_pair(_StartY, _StartX));

    while (!Que.empty())
    {
        int CurY = Que.front().first;
        int CurX = Que.front().second;
        Que.pop();

        for (int i = 0; i < 4; i++)
        {
            int TempY = CurY + SearchY[i];
            int TempX = CurX + SearchX[i];

            if (TempY < 0 || TempY >= maps.size() || TempX < 0 || TempX >= maps[0].size())
            {
                continue;
            }

            if (maps[TempY][TempX] == 'X')
            {
                continue;
            }

            if (Visited[TempY][TempX] == 'N')
            {
                continue;
            }

            Visited[TempY][TempX] = 'N';
            _Add += maps[TempY][TempX] - '0';
            Que.push(make_pair(TempY, TempX));
        }
    }
}

// X = 바다
// 숫자 = 무인도
// 연결된 무인도들의 숫자 합은 최대 무인도에서 머무를 수있는 날
// 연결된 섬들에서 각각 몇일 씩 머물 수 있는지 return, 아무것도 없으면 -1 리턴, 오름차순 정렬
vector<int> solution(vector<string> maps) 
{
    vector<int> answer;
    Visited.resize(maps.size(), "");
    for (size_t i = 0; i < maps.size(); i++)
    {
        for (size_t j = 0; j < maps[i].size(); j++)
        {
            char Temp = maps[i][j];
            if (Temp == 'X')
            {
                Visited[i] += 'N';
            }
            else
            {
                Visited[i] += 'Y';
            }
        }
    }

    for (size_t i = 0; i < maps.size(); i++)
    {
        for (size_t j = 0; j < maps[i].size(); j++)
        {
            if (Visited[i][j] == 'Y')
            {
                int Add = maps[i][j] - '0';
                Visited[i][j] = 'N';
                BFS(maps, i, j, Add);

                answer.push_back(Add);
            }
        }
    }

    if (!answer.empty())
    {
        sort(answer.begin(), answer.end());
    }
    else
    {
        answer.push_back(-1);
    }

    return answer;
}

[문제]

 

https://school.programmers.co.kr/learn/courses/30/lessons/17683

 

[풀이]

 

카카오 문제는 언제나 지문이 길지만, 막상 까보면 그냥 구현 문제이다. 지금까지 풀어왔던 카카오 문제와 유사한데, 정보가 긴 문자열(로그)로 주어지고, 그 로그의 정보를 해석하여 문제를 푸는 형식이다. 제한은 넉넉하다.

 

본인은 그냥 악보의 #을 따로 처리하기 번거로워서, 그냥 악보를 받고 먼저 보기 편한 방식으로 바꿔줬다(SheetMusic() 이후 ConvertSheetMusic()). 이후 반복문을 돌면서, 총 재생 시간은 몇 분인지, 노래 제목은 뭔지, 악보 구성은 어떻게 되어있는지 확인하고 검사 전 재생시간만큼 악보로 연주(?)를 해준다. 그러면 최종 검사 악보가 나오는데, 여기서 그냥 Find를 해주면 된다. -1 검사를 해도 되고 npos 검사를 실시해도 된다. 마지막으로 재생 시간이 더 길거나, 동일한 경우에는 앞서 재생된 노래를 return하기 위해 MaxPlayTime으로 조건을 판별해준다.

#include <string>
#include <vector>
#include <unordered_map>

using namespace std;
unordered_map<string, string> SheetMusicTable;

void SheetMusic()
{
    SheetMusicTable["C"] = "A";
    SheetMusicTable["C#"] = "B";
    SheetMusicTable["D"] = "C";
    SheetMusicTable["D#"] = "D";
    SheetMusicTable["E"] = "E";
    SheetMusicTable["F"] = "F";
    SheetMusicTable["F#"] = "G";
    SheetMusicTable["G"] = "H";
    SheetMusicTable["G#"] = "I";
    SheetMusicTable["A"] = "J";
    SheetMusicTable["A#"] = "K";
    SheetMusicTable["B"] = "L";
}

string ConvertSheetMusic(const string& _Input)
{
    string Result;
    for (size_t i = 0; i < _Input.length();)
    {
        string Note;
        if (i + 1 < _Input.length() && _Input[i + 1] == '#')
        {
            Note = _Input.substr(i, 2); // e.g., C#
            i += 2;
        }
        else
        {
            Note = _Input.substr(i, 1); // e.g., C
            i += 1;
        }

        auto it = SheetMusicTable.find(Note);
        if (it != SheetMusicTable.end())
        {
            Result += it->second;
        }
        else
        {
            Result += "?"; // Unknown note
        }
    }
    return Result;
}

string CreateSheetMusic(int _Miin, const string& _Score)
{
    string SheetMusic = "";
    int MaxIndex = _Score.size() - 1;
    int CurIndex = 0;
    while (_Miin--)
    {
        if (CurIndex > MaxIndex)
        {
            CurIndex = 0;
        }
        SheetMusic += _Score[CurIndex++];
    }

    return SheetMusic;
}

int MinuteCalculation(const string& _Start, const string& _End)
{
    string Start = _Start;
    string End = _End;

    int StartMin = (stoi(Start.substr(0, 2)) * 60) + stoi(Start.substr(3, 2));
    int EndMin = (stoi(End.substr(0, 2)) * 60) + stoi(End.substr(3, 2));

    return EndMin - StartMin;
}

// [1] 음악 제목 [2] 재생이 시작되고 끝난 시각 [3] 악보
// C, C#, D, D#, E, F, F#, G, G#, A, A#, B 12개
string solution(string m, vector<string> musicinfos) 
{
    string answer = "(None)";
    SheetMusic();
    string Newm = ConvertSheetMusic(m);

    int MaxPlayTime = -1; // 현재까지의 최장 재생 시간
    for (size_t i = 0; i < musicinfos.size(); i++)
    {
        string CurStr = musicinfos[i];
        int Min = MinuteCalculation(CurStr.substr(0, 5), CurStr.substr(6, 5));
        string Title = "";
        string Score = "";
        bool IsFirst = false;
        for (size_t j = 12; j < CurStr.size(); j++)
        {
            char temp = CurStr[j];
            if (temp != ',' && IsFirst == false)
            {
                Title += temp;
            }
            else if (temp == ',')
            {
                IsFirst = true;
                continue;
            }
            else
            {
                Score += temp;
            }
        }

        string NewScore = ConvertSheetMusic(Score);
        string Music = CreateSheetMusic(Min, NewScore);

        if (Music.find(Newm) != -1)
        {
            if (Min > MaxPlayTime)
            {
                MaxPlayTime = Min;
                answer = Title;
            }
        }
    }

    return answer;
}

 

[문제]

 

https://school.programmers.co.kr/learn/courses/30/lessons/169199?language=cpp

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

[풀이]

 

기본적인 구조는 DFS와 비슷하지만, 한 칸씩 이동하는 방식과 다르게 정해진 방향으로 쭉 이동해야합니다(인덱스 범위 내 or 다음 구역이 D가 아닌 경우). 이후 이동한 구역에 대해서는 현재까지의 이동 횟수(Count)를 기록합니다. 나중에 다시 돌아왔을 때 현재 Count보다 기록된 Count가 낮다면 해당 구역에서는 DFS를 다시 할 필요가 없기 때문입니다(이미 다른 구역을 모두 탐색한 곳이기 때문). 이런 식으로 이동하면서, 도달 가능한 방식을 찾고 그 중 가장 낮은 값을 리턴해줍니다.

#include <string>
#include <vector>
#include <algorithm>

using namespace std;
vector<vector<int>> Visited;
int LimitY = 0;
int LimitX = 0;
int CheckY[4] = {-1, 0, 1, 0};
int CheckX[4] = {0, 1, 0, -1};
int answer = INT32_MAX;
bool bIsFound = false;

void DFS(vector<string>& _board, int _StartY, int _StartX, int _Count)
{
    Visited[_StartY][_StartX] = _Count;

    // 위, 오른쪽, 아래, 왼쪽으로 진행 가능한지 체크
    // 진행이 불가능한 경우(이동 후 위치가 나와 동일한 경우)에는 실패 체크
    // 위에 반복
    for (size_t i = 0; i < 4; i++)
    {
        int TempY = _StartY;
        int TempX = _StartX;

        while (true)
        {
            TempY += CheckY[i];
            TempX += CheckX[i];

            if (TempY >= LimitY || TempY < 0 || TempX >= LimitX || TempX < 0) // 상하좌우가 이동 불가능한지 체크
            {
                TempY -= CheckY[i];
                TempX -= CheckX[i];
                break;
            }

            if (_board[TempY][TempX] == 'D')
            {
                TempY -= CheckY[i];
                TempX -= CheckX[i];
                break;
            }
        }

        if (Visited[TempY][TempX] <= _Count)
        {
            continue;
        }
        else if (_board[TempY][TempX] == 'G')
        {
            // 찾음
            bIsFound = true;
            answer = min(_Count, answer);
        }
        else
        {
            DFS(_board, TempY, TempX, _Count + 1);
        }
    }
}

int solution(vector<string> board) 
{
    int StartY = 0, StartX = 0;
    LimitY = board.size(); // 인덱스는 얘보다 -1 // 5
    LimitX = board[0].size(); // 인덱스는 얘보다 -1 // 7

    Visited.resize(board.size(), vector<int>(board[0].size(), INT32_MAX));

    // 원래 위치로 돌아오는 경우에는 폐기
    for (size_t y = 0; y < board.size(); y++)
    {
        for (size_t x = 0; x < board[y].size(); x++)
        {
            char CurDot = board[y][x];
            if ('R' == CurDot)
            {
                StartY = static_cast<int>(y);
                StartX = static_cast<int>(x);
            }
        }
    }

    int Count = 1;
    DFS(board, StartY, StartX, Count);

    if (bIsFound == false)
    {
        answer = -1;
    }

    return answer;
}

[문제]

 

https://school.programmers.co.kr/learn/courses/30/lessons/72411

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

[풀이]

 

가끔 보면 문제를 보자마자 힌트가 보이는 것들이 있습니다. 해당 문제가 그런데, orders의 크기(갯수)와 각 요소마다의 문자열 길이를 보면 이런 문제는 그냥 완전 탐색을 진행해도 됩니다.

 

근데 문제가 course를 선택해서 가장 많은 조합을 도출하라고 했으니, 아예 다 돌 필요는 없습니다. 일단 모든 orders를 취합해서 문자열들(메뉴들)이 뭐가 있는지를 파악합니다. 이후 이 문자열들에서 course 갯수만큼 추출해서 모든 요소들을 검사하고, 이 조합을 선택하는 orders가 몇개인지 파악합니다. 예를 들어 AB를 탐색하기로 했으면 ABCFG에서 AB가 있는지 없는지, AC에서 AB가 있는지 없는지를 파악하는 것입니다.

 

이렇게 2개 조합일 때, 3개 조합일 때, 4개 조합일 때 모두 파악한 뒤 가장 많은 선택을 받았던 조합을 answer에 담고 오름차순 정렬해준 뒤 return 해줍니다.

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <unordered_map>

using namespace std;

// 코스요리 메뉴는 최소 2가지 이상의 단품메뉴로 구성
// 최소 2명 이상의 손님으로부터 주문된 단품메뉴 조합에 대해서만 코스요리 메뉴 후보에 포함
// orders.size() : 2 이상 20 이하
// orders[i] : 2 이상 10 이하인 문자열, 대문자로만, 같은 알파벳이 중복해서 들어있지 않습니다
// course.size() : 1 이상 10 이하
// course[i] : 2 이상 10 이하인 자연수가 오름차순으로 정렬
// 각 코스요리 메뉴의 구성을 문자열 형식으로 배열에 담아 사전 순으로 오름차순 정렬해서 return
// 만약 가장 많이 함께 주문된 메뉴 구성이 여러 개라면, 모두 배열에 담아 return
vector<string> solution(vector<string> orders, vector<int> course) 
{
    vector<string> answer;
    map<char, int> Map;
    vector<char> Menus;

    for (size_t i = 0; i < orders.size(); i++)
    {
        for (size_t j = 0; j < orders[i].size(); j++)
        {
            char CurMenu = orders[i][j];
           
            if (Map.find(CurMenu) == Map.end())
            {
                Map.insert(make_pair(CurMenu, 1));
                Menus.push_back(CurMenu);
            }
        }
    }

    sort(Menus.begin(), Menus.end());

    // 원하는 조합 개수에 대해 순회
    for (int c : course)
    {
        unordered_map<string, int> CombCount;
        int MaxCount = 0;

        for (const string& order : orders)
        {
            if (order.size() < c) continue;

            string SortedOrder = order;
            sort(SortedOrder.begin(), SortedOrder.end());

            vector<bool> Select(SortedOrder.size(), false);
            fill(Select.end() - c, Select.end(), true);

            do
            {
                string Comb;
                for (int i = 0; i < SortedOrder.size(); ++i)
                {
                    if (Select[i])
                        Comb += SortedOrder[i];
                }

                CombCount[Comb]++;
                MaxCount = max(MaxCount, CombCount[Comb]);

            } while (next_permutation(Select.begin(), Select.end()));
        }

        // 가장 많이 등장한 조합만 answer에 추가
        for (const auto& it : CombCount)
        {
            const string& comb = it.first;
            int count = it.second;

            if (count >= 2 && count == MaxCount)
            {
                answer.push_back(comb);
            }
        }
    }

    sort(answer.begin(), answer.end());

    return answer;
}

[목차]

 

- 리플렉션 프로브를 위한 클래스 생성

- 결과 확인

 

 

[리플렉션 프로브를 위한 클래스 생성]

 

스카이박스에서 큐브맵을 로드하여 활용해봤습니다. 큐브맵을 여기서만 쓰는게 아니고, 리플렉션 프로브를 위해 사용할 수도 있습니다. 리플렉션 프로브는 3D 실시간 렌더링에서 간접 반사를 구현하기 위한 기술로, 오브젝트 표면에 환경이 반사되는 것처럼 보이게 만드는 데 사용됩니다.

 

실시간 반사는 성능 부담이 크기 때문에, 리플렉션 프로브를 활용하여 특정 위치의 반사 환경을 미리 저장해두고, 주변 오브젝트에 이 큐브맵을 적용하여 효율적으로 간접 반사를 구현할 수 있습니다.

 

바로 시작해보겠습니다. 클래스를 하나 만들어줍니다.

#pragma once
#include "Ext_Component.h"

class Ext_ReflectionComponent : public Ext_Component
{
public:
	// constrcuter destructer
	Ext_ReflectionComponent() {}
	~Ext_ReflectionComponent() {}

	// delete Function
	Ext_ReflectionComponent(const Ext_ReflectionComponent& _Other) = delete;
	Ext_ReflectionComponent(Ext_ReflectionComponent&& _Other) noexcept = delete;
	Ext_ReflectionComponent& operator=(const Ext_ReflectionComponent& _Other) = delete;
	Ext_ReflectionComponent& operator=(Ext_ReflectionComponent&& _Other) noexcept = delete;

	void ReflectionInitialize(std::shared_ptr<class Ext_Actor> _Owner, std::string_view _CaptureTextureName, const float4& _Scale = float4(128, 128));
	std::shared_ptr<class Ext_DirectXTexture> GetReflectionCubeTexture() { return CubeTexture; }

protected:
	
private:
	std::shared_ptr<class Ext_DirectXTexture> CubeTexture = nullptr;
	
};

 

ReflectionInitialize() 함수가 조금 중요한데, 다음과 같습니다.

void Ext_ReflectionComponent::ReflectionInitialize(std::shared_ptr<class Ext_Actor> _Owner, std::string_view _CaptureTextureName, const float4& _Scale/* = float4(128, 128)*/)
{
	Base_Directory Dir;
	Dir.MakePath("../Resource/FX/ReflectionTexture");
	std::string Path = Dir.GetPath();

	std::shared_ptr<Ext_DirectXRenderTarget> CaptureTarget = nullptr;

	if (nullptr == Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Forword.png")))
	{
		if (0 == _Scale.x || 0 == _Scale.y)
		{
			MsgAssert("ReflectionProbe : 캡쳐할 텍스쳐의 크기가 0 입니다");
			return;
		}

		if (nullptr == CaptureTarget)
		{
			CaptureTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM, _Scale, float4::ZERONULL);
		}

		float4 CenterPos = _Owner->GetTransform()->GetWorldPosition();
		float4 CenterRot = float4::ZERO;

		auto Scene = _Owner->GetOwnerScene().lock();
		if (Scene == nullptr)
		{
			MsgAssert("Scene이 유효하지 않습니다.");
			return;
		}

		// Forward
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot, float4(700, 700));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Forword.png"));

		// Back
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, 180, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Back.png"));

		// Right
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, 90, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Right.png"));

		// Left
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, -90, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Left.png"));

		// Top
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(-90, 0, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Top.png"));

		// Bottom
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(90, 0, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Bottom.png"));
		CaptureTarget->RenderTargetClear();

		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Forword.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Back.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Right.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Left.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Top.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Bottom.png");
	}

	CubeTexture = Ext_DirectXTexture::Find(_CaptureTextureName);

	if (nullptr == CubeTexture)
	{
		std::vector<std::shared_ptr<Ext_DirectXTexture>> CubeTextures;
		CubeTextures.reserve(6);

		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Right.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Left.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Top.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Bottom.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Forword.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Back.png")));

		CubeTexture = Ext_DirectXTexture::LoadCubeMap(_CaptureTextureName, CubeTextures);
	}
}

 

미리 준비된 Texture가 없으면 스크린샷을 찍어서 Texture들을 6개 만들고, 그걸로 바로 Load를 실시해서 CubeMap을 만들어줍니다.

 

CaptureCubeMap() 함수는 그냥 해당 방향의 장면을 RenderTarget에 그리는 함수입니다. 이후 이 값을 가지고 Ext_ScreenShoot::RenderTargetShoot_DxTex()를 실시합니다.

HRESULT Ext_ScreenShoot::RenderTargetShoot_DxTex(std::shared_ptr<Ext_DirectXRenderTarget> _CaptureTarget, std::string_view _Path, std::string_view _TextureName)
{
    if (nullptr == _CaptureTarget)
    {
        return S_FALSE;
    }

    ID3D11Texture2D* Resource = _CaptureTarget->GetTexture(0)->GetTexture2D();

    if (nullptr != Resource)
    {
        wchar_t PrevPath[255];
        GetCurrentDirectoryW(255, PrevPath); // 기존 경로 저장
        SetCurrentDirectoryW(Base_String::AnsiToUniCode(_Path).data()); // 경로 변경

        DirectX::ScratchImage image;
        HRESULT result = CaptureTexture(Ext_DirectXDevice::GetDevice(), Ext_DirectXDevice::GetContext(), Resource, image);
        if (SUCCEEDED(result))
        {
            result = DirectX::SaveToWICFile(image.GetImages(), image.GetImageCount(), DirectX::WIC_FLAGS_NONE, GUID_ContainerFormatPng, Base_String::AnsiToUniCode(_TextureName).data());
        }
        
        SetCurrentDirectoryW(PrevPath); // 이전 경로 되돌리기
    }

    return S_FALSE;
}

 

해당 함수는 마이크로소프트에서 만든 Capture() 함수를 프레임워크에 맞게 조금 변형한 함수인데, 이 함수를 쓰기 위해서는 DirectXTK 라이브러리가 필요합니다. 라이브러리 적용은 기존에 제가 설명드렸던 방식대로 동일하게 진행하면 됩니다.

 

위 과정을 진행하면 스크린샷이 생성됩니다.

 

이제 이 Texture들로 스카이박스때와 마찬가지로 Texture를 Load 해줍니다.

 

이 기능을 사용하려면 Scene에서 Actor를 Create()하고, 위치를 정해준 다음 SetReflection() 함수를 호출해주면 됩니다.

// 리플렉션
std::shared_ptr<ReflectionActor> ReflectionActor1 = CreateActor<ReflectionActor>("ReflectionActor1");
ReflectionActor1->GetTransform()->SetLocalPosition({ 0.f, 100.f, -100.f});
ReflectionActor1->SetReflection();

 

Actor 내부에 임의로 구현한 코드인데, 위에 기능들을 만들어놨으니 이대로 사용하면 됩니다.

void ReflectionActor::SetReflection()
{
	Reflection = std::make_shared<Ext_ReflectionComponent>();
	static int n = 0;
	MeshComp->SetSampler("CubeMapSampler", "CubeMapSampler");
	Reflection->ReflectionInitialize(GetSharedFromThis<Ext_Actor>(), "TestReflection" + std::to_string(n++), float4(512, 512));
	MeshComp->SetTexture(Reflection->GetReflectionCubeTexture(), "ReflectionTexture");
}

 

리플렉션용 CubeMap Pixel Shader는 기존 Path와 분리해놨습니다.

#include "LightData.fx"

Texture2D BaseColorTex : register(t0); // 텍스처 자원
TextureCube ReflectionTexture : register(t1);
SamplerState Sampler : register(s0); // 샘플러
SamplerState CubeMapSampler : register(s1); // 샘플러

struct PSInput
{
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD;
    float3 WorldPosition : POSITION0;
    float3 WorldNormal : NORMAL;
    float3 WorldTangent : TANGENT;
    float3 WorldBinormal : BINORMAL;
    float4 CameraWorldPosition : POSITION1;
};

struct PSOutPut
{
    float4 MeshTarget : SV_TARGET0;
    float4 PositionTarget : SV_TARGET1; // World Position Target
    float4 NormalTarget : SV_TARGET2; // World Normal Target
};

 // 각 벡터에 normalize를 해주는 이유는, 명시적으로 normalize된 벡터를 넣어줬다 하더라도 
 // 임의의 값이 어떻게 들어올지 모르기 때문에 그냥 해주는것(안정성을 위한 처리라고 보면 됨)
PSOutPut GraphicsCUBE_PS(PSInput _Input) : SV_TARGET
{
    PSOutPut Output = (PSOutPut) 0;

    float3 CameraPos = _Input.CameraWorldPosition;
    float3 ViewDir = normalize(CameraPos - _Input.WorldPosition);
    float3 Normal = normalize(_Input.WorldNormal);
    
    float3 ReflectDir = normalize(2.0f * Normal * dot(ViewDir, Normal) - ViewDir);
    float4 ReflectionColor = ReflectionTexture.Sample(CubeMapSampler, ReflectDir);
    
    float metallic = 1.0f;

    Output.MeshTarget = BaseColorTex.Sample(Sampler, _Input.TexCoord);
    Output.MeshTarget += float4(lerp(float3(0, 0, 0), ReflectionColor.rgb * 0.5f, metallic), 0.0f);

    Output.PositionTarget = float4(_Input.WorldPosition, 1.0f);
    Output.NormalTarget = float4(_Input.WorldNormal, 1.0f);

    return Output;
}

 

이렇게 해주면 내부에서 반사값이 적용된 뒤, 물체에 CubeMap 씌워집니다. 공식은 언리얼의 리플렉션 프로브와 동일합니다.

 

 

[결과 확인]

 

구체와 네모(Rect) 물체에 대해 적용하여 확인해봤습니다.

 

[목차]

 

- 큐브맵 제작을 위한 텍스쳐 준비

- SkyBox 세팅

- 결과 확인

 

 

[큐브맵 제작을 위한 텍스쳐 준비]

 

DirectX에서 지원하는 기능으로 큐브맵 SRV를 만들 수 있습니다. 아래는 Unity 가이드에서 보여주는 CubeMap 형태입니다.

 

DirectX에서 CubeMap을 로드할 때, Texture들을  [ +X, -X, +Y, -Y, +Z, -Z ] 순서로 넣어줘야 원하는 형태로 생성됩니다. 

 

바로 한 번 만들어 보겠습니다. 먼저 이미지를 준비합니다. 

 

CubeMap을 만들기 위해서는 위의 텍스쳐들이 Load된 상태여야 합니다. 따라서 다음과 같이 Load를 진행해줍니다.

Base_Directory Dir;
Dir.MakePath("../Resource/FX/ReflectionTexture/SkyBox");
std::vector<std::string> Paths = Dir.GetAllFile({ "png" });
		
std::vector<std::shared_ptr<Ext_DirectXTexture>> Texs;

for (const std::string& FilePath : Paths)
{
	Dir.SetPath(FilePath.c_str());
	std::string ExtensionName = Dir.GetExtension();
	std::string FileName = Dir.GetFileName();
	Texs.push_back(Ext_DirectXTexture::LoadTexture(FilePath.c_str()));
}

Ext_DirectXTexture::LoadCubeMap("SkyBox", Texs);

 

이러면 0번 사진부터 순서대로 로드하고 Texs 컨테이너에 담깁니다. 이후 LoadCubeMap() 함수로 전달해줍니다. 

// 큐브맵 텍스쳐 만들기, 이름 지정해주고 저장 및 로드
static std::shared_ptr<Ext_DirectXTexture> LoadCubeMap(std::string_view _Name, std::vector<std::shared_ptr<Ext_DirectXTexture>>& _Textures)
{
	std::shared_ptr<Ext_DirectXTexture> NewTexture = Ext_ResourceManager::CreateNameResource(_Name);
	NewTexture->CubeMapLoad(_Textures);

	return NewTexture;
}

 

여기서 기존과 같이 리소스 매니저 컨테이너에 등록되고, Load가 실행됩니다.

void Ext_DirectXTexture::CubeMapLoad(std::vector<std::shared_ptr<Ext_DirectXTexture>>& _Textures)
{
	Texture2DInfo = { 0 };

	if (_Textures.empty() || !_Textures[0])
	{
		MsgAssert("CubeMap 텍스처 리스트가 비었거나 첫 번째 텍스처가 null입니다.");
		return;
	}

	UINT Size = _Textures[0]->GetScale().ix();

	Texture2DInfo.ArraySize = 6;
	Texture2DInfo.Width = Size;
	Texture2DInfo.Height = Size;
	Texture2DInfo.Format = _Textures[0]->Texture2DInfo.Format;
	Texture2DInfo.SampleDesc.Count = 1;
	Texture2DInfo.SampleDesc.Quality = 0;
	Texture2DInfo.MipLevels = 1;
	Texture2DInfo.Usage = D3D11_USAGE_DEFAULT;
	Texture2DInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;
	Texture2DInfo.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_RENDER_TARGET | D3D11_BIND_FLAG::D3D11_BIND_SHADER_RESOURCE;

	D3D11_RENDER_TARGET_VIEW_DESC DescRTV;
	DescRTV.Format = Texture2DInfo.Format;
	DescRTV.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
	DescRTV.Texture2DArray.ArraySize = 6;
	DescRTV.Texture2DArray.FirstArraySlice = 0;
	DescRTV.Texture2DArray.MipSlice = 0;

	D3D11_SHADER_RESOURCE_VIEW_DESC DescSRV;

	DescSRV.Format = Texture2DInfo.Format;
	DescSRV.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
	DescSRV.TextureCube.MipLevels = 1;
	DescSRV.TextureCube.MostDetailedMip = 0;

	//Array to fill which we will use to point D3D at our loaded CPU images.
	D3D11_SUBRESOURCE_DATA pData[6];
	for (int cubeMapFaceIndex = 0; cubeMapFaceIndex < 6; cubeMapFaceIndex++)
	{
		DirectX::ScratchImage& CurImage = _Textures[cubeMapFaceIndex]->Image;

		pData[cubeMapFaceIndex].pSysMem = CurImage.GetImages()->pixels;
		pData[cubeMapFaceIndex].SysMemPitch = (UINT)CurImage.GetImages()->rowPitch;
		pData[cubeMapFaceIndex].SysMemSlicePitch = 0;
	}

	//Create the Texture Resource
	HRESULT TextureResult = Ext_DirectXDevice::GetDevice()->CreateTexture2D(&Texture2DInfo, &pData[0], &Texture2D);
	if (S_OK != TextureResult)
	{
		MsgAssert("큐브 텍스쳐 생성에 실패했습니다.");
		return;
	}

	COMPTR<ID3D11RenderTargetView> NewRTV = nullptr;
	HRESULT RTVResult = Ext_DirectXDevice::GetDevice()->CreateRenderTargetView(Texture2D, &DescRTV, &NewRTV);
	RTVs.push_back(NewRTV);

	if (S_OK != RTVResult)
	{
		MsgAssert("큐브 랜더타겟 뷰 생성에 실패했습니다.");
		return;
	}

	HRESULT SRVResult = Ext_DirectXDevice::GetDevice()->CreateShaderResourceView(Texture2D, &DescSRV, &SRV);
	if (S_OK != SRVResult)
	{
		MsgAssert("큐브 쉐이더 리소스 뷰 생성에 실패했습니다.");
		return;
	}
}

 

기존의 RTV, SRV를 만들던 것과 차이가 있다면, MiscFlags 값을 D3D11_RESOURCE_MISC_TEXTURECUBE로 지정해주는 것과 아래와 같이 D3D11_SUBRESOURCE_DATA에 값을 6번 넣어주고, 그걸로 Textrue2D를 Create()하는 것 정도 아닐까 싶습니다.

//Create the Texture Resource
HRESULT TextureResult = Ext_DirectXDevice::GetDevice()->CreateTexture2D(&Texture2DInfo, &pData[0], &Texture2D);
if (S_OK != TextureResult)
{
	MsgAssert("큐브 텍스쳐 생성에 실패했습니다.");
	return;
}

 

여기까지 했다면 CubeMap Texture의 SRV가 생성됩니다.

 

 

[SkyBox 세팅]

 

SkyBox는 기존에 렌더러들을 출력해주는 RenderTarget에 그리지 않고, 따로 RenderTarget을 만들어 거기에 먼저 그려둔 뒤, 나중에 최종 RenderTarget(CameraRenderTarget)에 Blending 했습니다. 먼저 Material Setting입니다.

std::shared_ptr<Ext_DirectXMaterial> NewRenderingPipeline = Ext_DirectXMaterial::CreateMaterial("SkyBox");

NewRenderingPipeline->SetVertexShader("SkyBox_VS");
NewRenderingPipeline->SetPixelShader("SkyBox_PS");
NewRenderingPipeline->SetBlendState("BaseBlend");
NewRenderingPipeline->SetDepthState("SkyDepth");
NewRenderingPipeline->SetRasterizer("SkyRasterizer");

 

Material Setting중 새로 설정한게 Depth State 입니다. SkyRasterizer는 그냥 NONE Culling 설정 Rasterizer입니다.

// SkyDepth
D3D11_DEPTH_STENCIL_DESC DepthStencilInfo = { 0, };

DepthStencilInfo.DepthEnable = true;
DepthStencilInfo.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
DepthStencilInfo.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
DepthStencilInfo.StencilEnable = false;

Ext_DirectXDepth::CreateDepthStencilState("SkyDepth", DepthStencilInfo);

 

이렇게 하면 깊이 테스트는 하지만, Depth Buffer에는 기록되지 않아 다른 오브젝트들이 스카이박스를 덮어씌울 수 있도록 해줍니다.

 

Camera 생성 전 SkyBoxUnit과 SkyBoxRenderTarget를 만들어줬습니다.

// 스카이박스용
SkyBoxRenderTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT, Base_Windows::GetScreenSize(), float4::ZERONULL);
SkyBoxTransform = std::make_shared<Ext_Transform>();
SkyBoxUnit.MeshComponentUnitInitialize("FullBox", "SkyBox");
std::shared_ptr<Ext_DirectXTexture> Tex = Ext_DirectXTexture::Find("SkyBox");
SkyBoxUnit.GetBufferSetter().SetTexture(Tex, "CubeMapTex");

 

Texture는 앞서 로드해둔 CubeMap Texture를 바인딩해줍니다. 새롭게 FullBox가 추가됐는데, FullRect와 동일하게 -1.0 ~ 1.0 범위를 갖는 Box라고 보시면 됩니다.

 

이제 SkyBox를 사용하는 Scene의 경우에 있어서, Rendering() 함수 실행 초기에 바로 Rendering을 먼저 수행합니다.

// 카메라의 MeshComponents들에 대한 업데이트 및 렌더링 파이프라인 리소스 정렬
void Ext_Camera::Rendering(float _Deltatime)
{
	if (true == bIsSkybox)
	{
		SkyBoxRendering();
	}
    //...
}

// 스카이박스 렌더링
void Ext_Camera::SkyBoxRendering()
{
	SkyBoxRenderTarget->RenderTargetClear();
	SkyBoxRenderTarget->RenderTargetSetting();

	float4 CamPos = GetTransform()->GetWorldPosition();
	SkyBoxTransform->SetLocalPosition(CamPos);
	SkyBoxTransform->SetCameraMatrix(GetTransform()->GetWorldPosition(), ViewMatrix, ProjectionMatrix);
	SkyBoxUnit.GetBufferSetter().SetConstantBufferLink("TransformData", *SkyBoxTransform->GetTransformData());
	SkyBoxUnit.Rendering(0.0f);
	SkyBoxUnit.GetBufferSetter().AllTextureResourceReset();
}

 

스카이 박스가 렌더링되는 Unit은 Camera를 계속 쫓아가는 형태로 만들어주고, Transform을 cbuffer로 바인딩해줍니다.

 

아래는 Shader 입니다.

////////////////////// SkyBos_VS
#include "Transform.fx"

struct VSInput
{
    float4 Position : POSITION;
};

struct VSOutput
{
    float4 Position : SV_POSITION;
    float3 TexCoord : TEXCOORD0;
};

VSOutput SkyBox_VS(VSInput _Input)
{
    VSOutput Output;
    float4x4 ViewMat = ViewMatrix;
    ViewMat._41 = 0;
    ViewMat._42 = 0;
    ViewMat._43 = 0;
    
    float4 VPosition = mul(_Input.Position, ViewMat);
    Output.Position = mul(VPosition, ProjectionMatrix);
    // Output.Position.w = 1.0f;
    Output.Position.z = Output.Position.w;
    Output.TexCoord = _Input.Position.xyz;
    
    return Output;
}

////////////////////// SkyBos_PS
TextureCube CubeMapTex : register(t0);
SamplerState Sampler : register(s0);

struct PSInput
{
    float4 Position : SV_POSITION;
    float3 TexCoord : TEXCOORD0;
};

float4 SkyBox_PS(PSInput _Input) : SV_TARGET
{
    float4 Color = CubeMapTex.Sample(Sampler, _Input.TexCoord);
    return Color;
}

 

기존과 다른 점은 Pixel Shader에서 Texture2D가 아니라, TextureCube를 사용한다는 점입니다. 마지막으로 Rendering() 함수 종료 전, CameraRenderTarget에 Blending(Merge) 해줍니다.

 

 

[결과 확인]

 

바로 결과를 확인해보겠습니다.

 

+ Recent posts