[문제]

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) 해줍니다.

 

 

[결과 확인]

 

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

 

[목차]

 

- 포스팅에 앞서

- 디퍼드 렌더링에서 반투명 물체 그리기

- 테스트 결과

 

 

[포스팅에 앞서]

 

해당 포스팅은 디퍼드 렌더링에서의 반투명 물체를 그릴 때 어떤 문제가 있는지 정도만 테스트했습니다. 

 

 

[디퍼드 렌더링에서 반투명 물체 그리기]

 

디퍼드 렌더링에서 가장 큰 문제는 바로 반투명 물체를 그리는 것입니다. 디퍼드 렌더링은 다음의 과정을 거칩니다.

 

1. G-Buffer Pass

: Geometry 정보를 여러 RenderTarget에 기록(Albedo, Normal, WorldPosition 등)

 

2. Lighting Pass

: 모든 조명을 G-Buffer 기반으로 계산

 

3. Final Composition

: 결과 조명값을 픽셀에 적용

 

디퍼드 렌더링은 한 픽셀 당 오직 하나의 Geometry 정보만 기록하기 때문에, 다수의 반투명 물체를 표현할 수 없습니다.

 

- 반투명 물체는 여러 픽셀이 겹쳐서 알파 블렌딩이 이뤄져야 하는데, G-Buffer는 마지막에 그려진 1개의 픽셀 정보만 기록함

- Lighting Pass는 G-Buffer를 기준으로 조명을 계산하기 때문에, 반투명 물체가 G-Buffer에 들어가지 않으면 조명 적용 자체가 불가능함

 

해당 문제는 이런 저런 방법이 있는데, 가장 쉽게 구현할 수 있는 방식이 바로 Alpha의 경우 Forward 렌더링을 따로 진행한 뒤, Depth Test 기반으로 서로 블렌딩 해주는 것입니다.

 

 

[테스트 결과]

 

디퍼드 렌더링 내에서 반투명 처리를 할 경우, 투과되어 모이는 물체에 빛이 적용되지 않는 현상이 발생합니다.

 

반투명을 Forward와 같이 따로 그리고, 그걸 나중에 RenderTarget에 Merge(Blending)하는 방식입니다(정확하게 수행한 것은 아님). 그러면 의도대로 반투명 처리가 되는 것을 확인할 수 있습니다.

 

 

[목차]

 

- 포스트 프로세싱이란

- 포스트 프로세싱 적용해보기

 

 

[포스트 프로세싱이란]

 

포스트 프로세싱은 3D 그래픽스에서 렌더링이 완료된 화면 이미지를 후처리하여 다양한 시각적 효과를 추가하는 단계를 말합니다. RenderTarget에 그려진 결과물(주로 G-Buffer)에 대해 추가로 이미지 필터를 적용하는 것입니다. 다양한 효과들이 있습니다.

 

- 블름(블러)

- 화면 전체 색감 보정

- 피사계 심도

- HDR 톤 매핑

- 안티 앨리어싱(FXAA)

- 디스토션

 

 

[포스트 프로세싱 적용해보기]

 

이제 프레임워크에 실제로 포스트 프로세싱을 적용해보겠습니다. 먼저 PostProcess 인터페이스를 담당하는 클래스를 만들어줍니다.

#pragma once
#include "Ext_DirectXRenderTarget.h"

class Ext_PostProcess : std::enable_shared_from_this<Ext_PostProcess>
{
	friend class Ext_DirectXRenderTarget;

public:
	std::shared_ptr<Ext_DirectXRenderTarget> GetPostTarget() { return PostTarget; }

protected:
	struct FrameData
	{
		float4 ScreenSize; // 화면(또는 텍스처) 크기
		float AccTime = 0.0f; // 실행된 시간(초)
	};

	virtual void Start() = 0;
	virtual void PostProcessing(Ext_DirectXRenderTarget* _MainRenderTarget, std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime) = 0;

	std::shared_ptr<Ext_MeshComponentUnit> PostUnit;
	std::shared_ptr<Ext_DirectXRenderTarget> PostTarget;

	FrameData FData;

private:
	
};

 

해당 인터페이스를 상속받아서 만들어지는 PostProcess들은 RenderTarget이 들고 있도록 해줍니다.

///////////// Ext_DirectXRenderTarget.h
//...
public:
// 포스트 프로세스 만들기
template<typename PostType>
std::shared_ptr<PostType> CreateEffect()
{
	std::shared_ptr<PostType> NewPostProcess = std::make_shared<PostType>();
	PostProcessInitialize(NewPostProcess);
	PostProcesses.push_back(NewPostProcess);
	return NewPostProcess;
}

void Ext_DirectXRenderTarget::PostProcessing(std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime)
{
	for (size_t i = 0; i < PostProcesses.size(); i++)
	{
		PostProcesses[i]->PostProcessing(this, _Camera, _DeltaTime);
	}
}

std::vector<std::shared_ptr<class Ext_PostProcess>>& GetPostProcesses() { return PostProcesses; }

private:
std::vector<std::shared_ptr<class Ext_PostProcess>> PostProcesses = {};

 

이렇게 RenderTarget에 대해 PostProcess를 추가하면 컨테이너 요소를 순회하면서 Update()가 진행될 것입니다.

 

 

 

// 카메라 생성 시 호출
void Ext_Camera::Start()
{
	ViewPortData.TopLeftX = 0;
	ViewPortData.TopLeftY = 0;
	ViewPortData.Width = Base_Windows::GetScreenSize().x;
	ViewPortData.Height = Base_Windows::GetScreenSize().y;
	ViewPortData.MinDepth = 0.0f;
	ViewPortData.MaxDepth = 1.0f;

	Width = ViewPortData.Width;
	Height = ViewPortData.Height;

	// 카메라 최종 렌더타겟
	CameraRenderTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT, Base_Windows::GetScreenSize(), float4::ZERONULL); // 해당 카메라의 최종 결과물 타겟
	CameraRenderTarget->CreateDepthTexture();
    // 추가할 PostProcess들
	// CameraRenderTarget->CreateEffect<Ext_Blur>();
	// CameraRenderTarget->CreateEffect<Ext_Distortion>();
	// CameraRenderTarget->CreateEffect<Ext_OldFilm>();
	// CameraRenderTarget->CreateEffect<Ext_TextureTest>();
    // ...
}

// 카메라의 MeshComponents들에 대한 업데이트 및 렌더링 파이프라인 리소스 정렬
void Ext_Camera::Rendering(float _Deltatime)
{
	// ...
    // 모든 렌더링 과정이 끝난 후 실시해준다.
	CameraRenderTarget->PostProcessing(GetSharedFromThis<Ext_Camera>(), _Deltatime);
}

 

0. 렌더링 파이프라인 세팅

: Vertex Shader, Pixel Shader만 다르고 Blend State, Depth State, Rasterzier State, Sampler State는 모두 같습니다.

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

NewRenderingPipeline->SetVertexShader("XXXEffect_VS");
NewRenderingPipeline->SetPixelShader("XXXEffect_PS");
NewRenderingPipeline->SetBlendState("BaseBlend");
NewRenderingPipeline->SetDepthState("AlwayDepth");
NewRenderingPipeline->SetRasterizer("NonCullingRasterizer");

// Blend State : 알파 블렌드
D3D11_BLEND_DESC BlendInfo = { 0, };

BlendInfo.AlphaToCoverageEnable = false;
BlendInfo.IndependentBlendEnable = false;
BlendInfo.RenderTarget[0].BlendEnable = true;
BlendInfo.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; // 자주 쓰는 조합 1
BlendInfo.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
BlendInfo.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
BlendInfo.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
BlendInfo.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE;
BlendInfo.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
BlendInfo.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

// Depth State : 깊이 테스트 진행 X
D3D11_DEPTH_STENCIL_DESC DepthStencilInfo = { 0, };

DepthStencilInfo.DepthEnable = true;
DepthStencilInfo.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
DepthStencilInfo.DepthFunc = D3D11_COMPARISON_ALWAYS;
DepthStencilInfo.StencilEnable = false;

// Rasterizer State : Back-FaceCuliiing 안하는 Rasterizer
D3D11_RASTERIZER_DESC Desc = {};

Desc.CullMode = D3D11_CULL_NONE;
Desc.FrontCounterClockwise = FALSE;
Desc.FillMode = D3D11_FILL_SOLID;

 

 

1. Blur(Bloom)

: 가우시안 블러를 사용했습니다.

#include "PrecompileHeader.h"
#include "Ext_Blur.h"

#include <DirectX11_Base/Base_Windows.h>
#include "Ext_MeshComponentUnit.h"

void Ext_Blur::Start()
{
	PostUnit = std::make_shared<Ext_MeshComponentUnit>();
	PostUnit->MeshComponentUnitInitialize("FullRect", "Blur");
	PostTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT, Base_Windows::GetScreenSize(), float4::ZERONULL);
}

void Ext_Blur::PostProcessing(Ext_DirectXRenderTarget* _MainRenderTarget, std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime)
{
	PostTarget->RenderTargetClear();
	PostTarget->RenderTargetSetting();

	FData.ScreenSize = Base_Windows::GetScreenSize();

	PostUnit->GetBufferSetter().SetConstantBufferLink("FrameData", FData);
	PostUnit->GetBufferSetter().SetTexture(_MainRenderTarget->GetTexture(0), "DiffuseTex");
	PostUnit->Rendering(_DeltaTime);
	PostUnit->GetBufferSetter().AllTextureResourceReset();

	_MainRenderTarget->RenderTargetClear();
	_MainRenderTarget->Merge(PostTarget);
}
// 가우시안 커널
// Total = 16+64+96+64+16 = 256
static float Gau[5][5] =
{
    { 1, 4, 6, 4, 1 },
    { 4, 16, 24, 16, 4 },
    { 6, 24, 36, 24, 6 },
    { 4, 16, 24, 16, 4 },
    { 1, 4, 6, 4, 1 }
};

// 7×7 가우시안 커널 (Pascal row 6: 1, 6, 15, 20, 15, 6, 1)
// 전체 가중치 합 = (1+6+15+20+15+6+1)² = 64² = 4096
static const float Gau7[7][7] =
{
    { 1, 6, 15, 20, 15, 6, 1 },
    { 6, 36, 90, 120, 90, 36, 6 },
    { 15, 90, 225, 300, 225, 90, 15 },
    { 20, 120, 300, 400, 300, 120, 20 },
    { 15, 90, 225, 300, 225, 90, 15 },
    { 6, 36, 90, 120, 90, 36, 6 },
    { 1, 6, 15, 20, 15, 6, 1 }
};

// 9×9 가우시안 커널 (Pascal row 8: 1, 8, 28, 56, 70, 56, 28, 8, 1)
// 전체 가중치 합 = (1+8+28+56+70+56+28+8+1)² = 256² = 65536
static const float Gau9[9][9] =
{
    { 1, 8, 28, 56, 70, 56, 28, 8, 1 },
    { 8, 64, 224, 448, 560, 448, 224, 64, 8 },
    { 28, 224, 784, 1568, 1960, 1568, 784, 224, 28 },
    { 56, 448, 1568, 3136, 3920, 3136, 1568, 448, 56 },
    { 70, 560, 1960, 3920, 4900, 3920, 1960, 560, 70 },
    { 56, 448, 1568, 3136, 3920, 3136, 1568, 448, 56 },
    { 28, 224, 784, 1568, 1960, 1568, 784, 224, 28 },
    { 8, 64, 224, 448, 560, 448, 224, 64, 8 },
    { 1, 8, 28, 56, 70, 56, 28, 8, 1 }
};

cbuffer FrameData : register(b0)
{
    float4 ScreenSize; // 화면 크기
    float AccTime; // 실행된 시간
};

Texture2D DiffuseTex : register(t0);
SamplerState Sampler : register(s0);

struct PSInput
{
    float4 Position : SV_POSITION;
    float2 Texcoord : TEXCOORD;
};

// 5x5 가우시안 블러
//float4 Blur_PS(PSInput _Input) : SV_TARGET
//{
//    // 픽셀 하나 사이즈는 몇입니까
//    float2 PixelSize = float2(1.0f / ScreenSize.x, 1.0f / ScreenSize.y);
    
//    // UV 중심에서 2픽셀씩 왼쪽위로 이동하여 가우시안 시작 지점 정하기
//    float2 PixelUvCenter = _Input.Texcoord.xy;
//    float2 StartUV = _Input.Texcoord.xy + (-PixelSize * 2.0f);
//    float2 CurUV = StartUV;
//    float4 ResultColor = (float4) 0.0f;
 
//    // 5×5 샘플링 & 가중치 누적
//    for (int y = 0; y < 5; ++y)
//    {
//        for (int x = 0; x < 5; ++x)
//        {
//            ResultColor += DiffuseTex.Sample(Sampler, CurUV.xy) * Gau[y][x];
//            CurUV.x += PixelSize.x;
//        }
        
//        CurUV.x = StartUV.x;
//        CurUV.y += PixelSize.y;
//    }
    
//    // 가중치 총합 256 으로 정규화
//    // 이렇게 해야 모든 샘플 값이 커널 가중치만큼 더해져서 전체 이미지가 지나치게 밝아지거나 어두워지는 것 방지
//    ResultColor /= 256.0f;
    
//    return ResultColor;
//}

// 7x7 가우시안 블러
//float4 Blur_PS(PSInput _Input) : SV_TARGET
//{
//    // 픽셀 하나 사이즈는 몇입니까
//    float2 PixelSize = float2(1.0f / ScreenSize.x, 1.0f / ScreenSize.y);
    
//    // UV 중심에서 2픽셀씩 왼쪽위로 이동하여 가우시안 시작 지점 정하기
//    float2 PixelUvCenter = _Input.Texcoord.xy;
//    float2 StartUV = _Input.Texcoord.xy + (-PixelSize * 3.0f);
//    float2 CurUV = StartUV;
//    float4 ResultColor = (float4) 0.0f;
 
//    // 7×7 샘플링 & 가중치 누적
//    for (int y = 0; y < 7; ++y)
//    {
//        for (int x = 0; x < 7; ++x)
//        {
//            ResultColor += DiffuseTex.Sample(Sampler, CurUV.xy) * Gau7[y][x];
//            CurUV.x += PixelSize.x;
//        }
        
//        CurUV.x = StartUV.x;
//        CurUV.y += PixelSize.y;
//    }
    
//    // 가중치 총합 256 으로 정규화
//    // 이렇게 해야 모든 샘플 값이 커널 가중치만큼 더해져서 전체 이미지가 지나치게 밝아지거나 어두워지는 것 방지
//    ResultColor /= 4096.0f;
       
//    return ResultColor;
//}

// 9x9 가우시안 블러
float4 Blur_PS(PSInput _Input) : SV_TARGET
{
    // 픽셀 하나 사이즈는 몇입니까
    float2 PixelSize = float2(1.0f / ScreenSize.x, 1.0f / ScreenSize.y);
    
    // UV 중심에서 2픽셀씩 왼쪽위로 이동하여 가우시안 시작 지점 정하기
    float2 PixelUvCenter = _Input.Texcoord.xy;
    float2 StartUV = _Input.Texcoord.xy + (-PixelSize * 4.0f);
    float2 CurUV = StartUV;
    float4 ResultColor = (float4) 0.0f;
 
    // 7×7 샘플링 & 가중치 누적
    for (int y = 0; y < 9; ++y)
    {
        for (int x = 0; x < 9; ++x)
        {
            ResultColor += DiffuseTex.Sample(Sampler, CurUV.xy) * Gau9[y][x];
            CurUV.x += PixelSize.x;
        }
        
        CurUV.x = StartUV.x;
        CurUV.y += PixelSize.y;
    }
    
    // 가중치 총합 256 으로 정규화
    // 이렇게 해야 모든 샘플 값이 커널 가중치만큼 더해져서 전체 이미지가 지나치게 밝아지거나 어두워지는 것 방지
    ResultColor /= 65536.0f;
    
    return ResultColor;
}

 

9x9까지 해버리면 연산량이 너무 많아집니다. 이에 대한 해결책으로 [원본 이미지 다운 스케일링 -> 5x5정도의 필터 적용 -> 적용된 이미지 원래 크기로 업스케일링]을 실시할 수도 있습니다(효과는 동일한데 연산은 최적화됨). 여기서는 적용에 의의를 두는 것이기 때문에 5x5, 7x7, 9x9를 각각 실시해봤습니다.

 

 

2. OldFilm

: 특수한 텍스쳐를 사용했습니다.

 

예전에 Cuphead라는 게임을 모작할 때 사용했던 리소스인데, 똑같이 가져와서 적용해봤습니다.

 

#include "PrecompileHeader.h"
#include "Ext_OldFilm.h"

#include <DirectX11_Base/Base_Windows.h>
#include "Ext_MeshComponentUnit.h"

void Ext_OldFilm::Start()
{
	// 기본 텍스쳐들 로드
	{
		Base_Directory Dir;
		Dir.MakePath("../Resource/FX/ScreenFX");
		std::vector<std::string> Paths = Dir.GetAllFile({ "png" });
		for (const std::string& FilePath : Paths)
		{
			Dir.SetPath(FilePath.c_str());
			std::string ExtensionName = Dir.GetExtension();
			std::string FileName = Dir.GetFileName();
			Textures.push_back(Ext_DirectXTexture::LoadTexture(FilePath.c_str()));
		}
	}

	OFData.OldFilmValue.x = 3.f;
	MaxIndex = static_cast<int>(Textures.size()) - 1;

	PostUnit = std::make_shared<Ext_MeshComponentUnit>();
	PostUnit->MeshComponentUnitInitialize("FullRect", "OldFilm");
	PostTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT, Base_Windows::GetScreenSize(), float4::ZERONULL);
}

void Ext_OldFilm::PostProcessing(Ext_DirectXRenderTarget* _MainRenderTarget, std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime)
{
	AccTime += _DeltaTime;
	if (AccTime >= 0.1f)
	{
		AccTime = 0.0f;
		++CurIndex;
		if (CurIndex > MaxIndex)
		{
			CurIndex = 0;
		}
	}

	PostUnit->GetBufferSetter().SetTexture(Textures[CurIndex], "DiffuseTex");
	PostUnit->GetBufferSetter().SetConstantBufferLink("OldFilmData", OFData);
	PostTarget->RenderTargetClear();
	PostTarget->RenderTargetSetting();

	PostUnit->Rendering(_DeltaTime);
	PostUnit->GetBufferSetter().AllTextureResourceReset();

	_MainRenderTarget->RenderTargetSetting();
	_MainRenderTarget->Merge(PostTarget);
}

 

cbuffer OldFilmData : register(b0)
{
    float4 OldFilmValue; // x 성분만 사용
}

Texture2D DiffuseTex : register(t0);
SamplerState Sampler : register(s0);

struct PSInput
{
    float4 Position : SV_POSITION;
    float2 Texcoord : TEXCOORD0;
};

float4 OldFilm_PS(PSInput IN) : SV_TARGET
{
    // 1) 스크린 얼룩(Film) 텍스처 샘플
    float4 FlimColor = DiffuseTex.Sample(Sampler, IN.Texcoord);

    // 2) 얼룩 마스크 강도 계산 (1?R) * OldFilmValue.x
    float Mask = (1.0f - FlimColor.r) * OldFilmValue.x;

    // 3) R, G, B, A 모두 동일한 마스크 값으로
    return float4(Mask, Mask, Mask, Mask);
}

 

 

3. Distortion

: 가장 흔하게 사용되는 왜곡 효과입니다. 보통 특정 부분만 마스킹해서 사용하는데, 여기서는 그냥 화면 대상 모든 픽셀에 적용해봤습니다.

#include "PrecompileHeader.h"
#include "Ext_Distortion.h"

#include <DirectX11_Base/Base_Windows.h>
#include "Ext_MeshComponentUnit.h"

void Ext_Distortion::Start()
{
	PostUnit = std::make_shared<Ext_MeshComponentUnit>();
	PostUnit->MeshComponentUnitInitialize("FullRect", "Distortion");
	PostTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT, Base_Windows::GetScreenSize(), float4::ZERONULL);	
}

void Ext_Distortion::PostProcessing(Ext_DirectXRenderTarget* _MainRenderTarget, std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime)
{
	PostTarget->RenderTargetClear();
	PostTarget->RenderTargetSetting();

	FData.ScreenSize = Base_Windows::GetScreenSize();
	FData.AccTime += _DeltaTime;

	PostUnit->GetBufferSetter().SetConstantBufferLink("FrameData", FData);
	PostUnit->GetBufferSetter().SetTexture(_MainRenderTarget->GetTexture(0), "DiffuseTex");
	PostUnit->Rendering(_DeltaTime);
	PostUnit->GetBufferSetter().AllTextureResourceReset();

	_MainRenderTarget->RenderTargetClear();
	_MainRenderTarget->Merge(PostTarget);
}
cbuffer FrameData : register(b0)
{
    float4 ScreenSize; // 화면 크기
    float AccTime; // 실행된 시간
};

Texture2D DiffuseTex : register(t0);
SamplerState Sampler : register(s0);

struct PSInput
{
    float4 Position : SV_POSITION; // 스크린 위치 (필요 없으면 생략 가능)
    float2 Texcoord : TEXCOORD0; // 0~1 범위 UV
};

float4 Distortion_PS(PSInput _Input) : SV_TARGET
{
    float2 UV = _Input.Texcoord;

    // 파라미터, 상수버퍼로 전달하면 조절 가능
    const float Frequency = 10.0f; // 파동 수
    const float Ample = 20.0f; // 진폭 분모

    // 공식
    UV.x += sin(UV.y * Frequency + AccTime) / Ample;

    return DiffuseTex.Sample(Sampler, UV);
}

 

 

[렌더링 파이프라인이란]

 

렌더링을 하기 위해서는 렌더링 파이프라인 개념을 알아야합니다. 렌더링 파이프라인은 Direct3D에서 메모리 자원들을 GPU로 처리하여 하나의 렌더링 이미지로 만드는 일련의 과정을 말합니다.

 

일련의 과정에서 크게 두 가지로 나눌 수 있기도 합니다. 고정 기능 단계와 프로그래밍 가능 단계입니다.

 

- 고정 기능 단계 : 미리 정해진 특정 연산들이 수행되고, 상태 객체라는 개념을 이용하여 연산의 설정을 변경할 수 있으며, 사용자가 임의로 실행을 거부할 수 없음

- 프로그래밍 가능 단계 : HLSL(High Level Shading Languege)로 셰이더 프로그래밍이 가능한 단계이며, 임의로 실행을 거부할 수 있음

 

 

[단계별 정리]

 

단계별 핵심 내용입니다. "렌더링 결과물을 출력한다"를 위해서는 Input Assembler, Vertex Shader, Rasterizer, Pixel Shader, Output Merger만 진행하면 됩니다. Hull Shader, Tessellator, Domain Shader, Geometry Shader는 인스턴싱을 위한 과정입니다. 대표적으로 파티클이 있습니다만, 제가 사용할 프레임워크에서는 크게 다루지 않을 것 같습니다.

Input Assembler
(입력 조립기 단계)
- Vertex Buffer / Index Buffer에 저장된 정점, 인덱스 데이터 수집
- 이 단계에서는 아직 쉐이더가 실행되지 않았으며, 파이프라인에 데이터가 들어오는 입구에 해당
- 설정 정보에는 [Input Layout], [Primitive Topology] 등도 포함

<Index Buffer 활용>
: Vertex Buffer와 관련이 깊으며, 정점 버퍼에 있는 각 정점의 위치를 기록하기 위해 활용
: 이를 통해 Vertex Buffer의 특정 정점(Vertex)을 빠르게 찾거나 정점을 재사용 가능하게 만들어주며, Input Assembler 단계에서 사용
: 예시) 삼각형 하나를 이루기 위한 인덱스: 0, 1, 2 → 정점 0~2를 참조
: 사용 시 중복 정점 제거로 메모리를 절약하며, 그리기 명령 효율성 증가

<Vertex Buffer 활용>
: 정점(Vertex)들의 위치, 노멀, UV, 색상 등(float3 Postion, float3 Normal, float3 TexCoord)의 속성을 담고 있는 버퍼로, Input Assembler에서 활용됨
: IASetVertexBuffers()로 설정 → Vertex Shader
Vertex Shader
(정점 셰이더)
- 각 정점 데이터를 변환하는 첫 번째 쉐이더
- 정점마다 월드 좌표 → 클립 좌표로 변환, 애니메이션 스키닝, 라이팅 계산 등의 처리 수행
- 여기서 수행된 결과는 Pixel Shader에서 사용할 수 있도록 전달

<Constant Buffer 활용>
: 셰이더에 값을 저장하는 메모리로, CPU ↔ GPU간 데이터를 전달에 사용되며, 모든 셰이더 단계에서 활용됨
: 해당 값에는 행렬(World, View, Projection 등) 정보 등 활용
: 최대 14슬롯 바인딩이 가능하며, 내부 데이터는 16byte 단위로 정렬 필요

<Sampler 활용>
: 샘플러를 쓰기도 하지만, 주로 픽셀 셰이더에서 씀
Hull Shader
(덮개 셰이더)
[생략 가능]
- 테셀레이션의 첫 번째 단계
- 패치(기본 도형) 단위로 테셀레이션 정도를 결정하기 위해 테셀레이션 계수를 계산
- 쉐이더가 실행되면 정점 그룹 단위로 계산되며, 곡면 분할이 필요한 경우 사용

<Constant Buffer 활용>
Tessellator
(테셀레이터)
[생략 가능]
- 고정 기능 하드웨어
- 직접 프로그래밍은 불가능하며, Hull Shader에서 제공된 정보를 기반으로 세분화된 정점을 생성
- 삼각형, 선, 사각형 형태로 분할 가능
Domain Shader
(영역 셰이더)
[생략 가능]
- 테셀레이터가 생성한 세분화된 정점의 위치와 속성을 계산
- 곡면의 실제 형상 및 기하 정보를 계산하며, 여기서 생성된 정점은 이후 Geometry Shader로 전달

<Constant Buffer 활용>
Geometry Shader
(기하 셰이더)
[생략 가능]
- 도형(Primitive) 단위의 생성, 제거, 변형
- 삼각형을 분해하거나 더 추가할 수 있고, 쉐도우 볼륨, 실루엣, 아웃라인 효과 등에 활용
- Stream Output을 통해 이 결과를 바로 GPU 버퍼에 저장할 수도 있음

<Stream Output (스트림 출력)>
: Geometry Shader 출력 결과를 GPU 메모리에 직접 저장(Geometry Shader → Stream Output → 버퍼 저장)
: 파티클, 물리 시뮬레이션, LOD 캐싱, 후속 드로우콜 등에 활용
: 렌더링 외의 데이터 가공 목적에도 활용

<Constant Buffer 활용>
Rasterizer
(레스터라이저)
- 벡터 → 픽셀 변환 (Triangle → Pixel)
- 화면 공간으로 변환된 정점 데이터를 픽셀 단위로 분해
- Z-버퍼, 컬링, 클리핑 등의 테스트가 여기서 수행
Pixel Shader
(픽셀 셰이더)
- 화면에 찍힐 각 픽셀의 색상 계산
- 텍스처, 라이팅, 그림자, 블렌딩, 쉐도우맵 등 다양한 시각 효과 구현의 중심
- 결과는 렌더 타겟으로 전달

<Constant Buffer 활용>
: 정점 셰이더에서 Transform 데이터를 주로 활용한다면, 여기는 텍스쳐, Structured(빛, 델타타임) 등을 활용

<Sampler 활용>
: 텍스처 필터링이나 경계 처리 방식 등을 정의한 값으로, Vertex Shader나 PixelShader에서 텍스처 샘플링 시 활용됨
: 상수버퍼와 마찬가지로 바인딩 슬롯으로 데이터가 전달되고 사용됨
: UV값에 WRAP, CLAMP 등을 설정
: SetSamplers()로 최대 16개까지 바인딩 가능

<Texture Buffer 활용>
: 텍스처, 노이즈맵, 그림자 맵 등 샘플링 가능한 데이터 자원이며, Pixel Shader, Compute Shader 등에 활용됨
: Diffuse, Normal, Shadow Map 등이 있으며 SRV(Shader Resource View)로 접근 가능함
: 최대 128개까지 바인딩 가능
Output Merger
(출력 병합)
- 최종 픽셀 결과를 렌더 타겟, 깊이/스텐실 버퍼에 기록
- Z-Test, 스텐실 테스트, 블렌딩 등을 수행하며 최종 출력 픽셀을 결정
- 모든 테스트를 통과한 픽셀만 화면에 그려짐

 

'DirectX11 > 그래픽스' 카테고리의 다른 글

[Graphics] 드로우콜(Draw Call)  (0) 2025.06.11
[Graphics] 포워드 렌더링, 디퍼드 렌더링  (0) 2025.06.11
[Graphics] Normal Mapping  (0) 2025.06.07
[Graphics] Normal  (0) 2025.06.06
[Graphics] Lighting  (2) 2025.06.05

+ Recent posts