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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


문제 내용 :

0.인자값으로 s와 skip 이라는 알파벳 소문자 문자열과 index라는 int 정수값이 주어짐

1.s 문자열에 문자를 index 만큼의 뒤의 문자로 변환해야함

2.변환시에 skip이라는 문자열에 해당 문자가 있을경우 그 문자를 무시하고 한번 더 변환함

2-1.변환시 글자 'z'를 넘어가는경우 'a'부터 다시 시작

이때 변환되는 문자열을 return


풀이 : 

문제에서 소문자 알파벳만 사용하기에 'a'~'z'를 담고있는 알파벳 배열을 만들것,

skip문자열의 문자인 경우 배열에 넣지 않으며 알파벳 배열을 만든다.

 

그후 s문자열의 길이만큼 반복문을 순회하며

해당 단일문자가 알파벳배열의 어디 index에 있는지 찾고 

찾은 문자의 index에 인자값index을 더하여 위치를 찾는다.

만약 더한 index값이 알파벳배열의 수를 넘었을 경우 , 다시 'a'부터 시작하게 초기화 한후 찾는다.

 

그럼 최종으로 찾은 index값을 알파벳 배열에 넣어 answer에 추가시키면 된다.

 

코드영역

 

더보기
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;


string solution(string s, string skip, int index) {
    string answer = "";
    
    
    //sort 정렬 : 시간복잡도 N , 우선순위 큐 : 시간복잡도 : log n
    //이기에 우선순위큐를 이용하여 minheap으로 정렬함
    priority_queue<char,vector<char>,greater<char>> pq_skip;
    for(char ch : skip) {  pq_skip.push(ch);}
    
    
    char pivot = 'a'; //알파벳 배열에 넣을 시작 문자
    vector<char> alpabet;
    while(pivot <= 'z') 
    {
        //해당 알파벳이 skip문자열에 있는경우 알파벳 배열에 넣지 않는다.
        if( pq_skip.top() != pivot) { alpabet.push_back(pivot); }
        else { pq_skip.pop(); }
        pivot++;
    }
        
    //문자열 s 길이만큼 순회하며 문자를 변환한다.
    int result;
    for(int i = 0; s.size() >i;i++)
    {
        //문자열 s의 단일문자가 알파벳배열의 어디에 위치하는지 찾는다.
        int alpabet_index = find(alpabet.begin(),alpabet.end(),s[i]) - alpabet.begin();
        
        //찾은 단일문자위치에 인자값index를 더하여 최종 위치를 찾는다.
        result = alpabet_index + index;
        //만약 알파벳 사이즈를 초과한 경우 , 알파벳 사이즈만큼 뺀다.
        while(result> alpabet.size()-1 )
        {
            result -= alpabet.size();
        }
        
        answer += alpabet[result];
    }
    
    
    return answer;
}

해당 문제를 이렇게 풀었지만 , 다른 사람 풀이를 보니 더욱 가독성 좋고 쉽게 풀이한게 있어 코드를 첨부한다.

 

다른사람 풀이

#include <string>
#include <vector>

using namespace std;

string solution(string s, string skip, int index) {
    string answer = "";

    for(auto v : s)
    {
        int t = 0; //다음 문자로 넘어간 숫자를 카운트하는 변수
        int c = v - 'a'; //문자v에서 'a'를 뺌으로 알파벳의 위치순서를 파악
        while(t < index)
        {
            c++;//다음 알파벳위치(알파벳 변환)
            v = (c % 26) + 'a';//해당 알파벳 위치를 문자열로 변환(v는 char 자료형)
            //c%26은 알파벳이 총 26개 이기 때문에 큰 숫자가 들어와도 알파벳의 한 형태로 변환 가능하기떄문
            
            //skip문자열에 해당 문자(v)가 있는지 체크
            if(skip.find(v) == string::npos)
            {
            	//없으면 카운트를 증가시킴
                t++;
            }
        }
        
        answer += v;
    }
    return answer;
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV3.디스크 컨트롤러  (0) 2024.04.08
LV2.가장 큰 수  (0) 2023.11.03
LV1.체육복  (0) 2023.10.26
LV2.택배상자  (0) 2023.10.26
LV2.롤케이크 자르기  (0) 2023.10.26

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


문제 내용 :

1.체육복이 없는 학생에게 여벌의 체육복이 있는 학생들이 빌려줄 수 있음

1-1.하지만 빌려줄수 있는 경우는 자기 앞 또는 뒤 학생한테만 체육복을 빌려 줄 수 있음

2.체육복은 가지고 있지만 한벌만 가지고있으면 빌려 줄수 없음

3.체육복을 가지고 있어야만 수업을 들을 수 있음

4.여벌 체육복을 갖고있지만 체육복을 한벌 잃어버릴 수 있음

이때 수업을 들을 수 있는 학생의 최대값을 return


풀이 : map자료구조 

학생을 나타내는 key , 체육복의 갯수 value 데이터가 필요하기에 map 자료구조를 활용

1.체육복이 없는 학생들(lost)

2.여벌체육복이 있는 학생들(reserve)

이 두 무리를 하나의 map으로 정리한다.

이 map에 존재 하지 않는 학생은 수업을 들을 수 있는 학생이고

이 map에 있는 학생들에 대해서만 체크하면 된다.

 

그후 map을 학생수(n)만큼 순회하며 앞,뒤를 체크하며 체육복 수를 조정하면 된다.

 

코드 영역

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

using namespace std;

//체육복 빌려주는 조건 : 자기 앞,뒷 1번호 만큼 가능
//체육수업을 들을수있는 학생의 최댓값 return 
//여벌 체육복을 가져온 학생도 도난 가능,체육복을 빌려줄수 없음 하지만 자기것는 있음

bool reserve_check(int index, map<int,int>& students)
{
    //iter = 자신의 앞 또는 뒤 학생의 iter
    map<int,int>::iterator iter_before = students.find(index);
    
    //해당 학생이 없을수도 있기에(제일 앞학생 또는 제일 뒷학생 고려)
    //여벌 체육복이 있으면 해당 학생의 체육복을 -1 시킴
    if(iter_before  != students.end() && iter_before->second == 2)
    {
        iter_before->second--;
        return true;
    }  
    
    return false;
}


int solution(int n, vector<int> lost, vector<int> reserve) {
    int answer = 0;
    
    map<int,int> students; //학생번호,체육복 수
    
    //여벌 체육복이 있는 학생을 맵에 추가
    for(int num : reserve) {  students.insert({num,2}); }
    
    //체육복을 잃어버린 학생을 맵에 추가
    for(int num : lost)
    {
        //해당 맵에 여벌 체육복을 갖고 있지만 잃어버린 학생이 있을수 있기에 find 실행
         map<int,int>::iterator iter = students.find(num);
        //find결과가 없으면 해당 체육복이 없는 학생으로 추가
        if(iter  == students.end()) {  students.insert({num,0}); }
        //find 결과가 있으면 해당 학생의 체육복 수를 1감소
        else {  iter->second--;}
    }
    
    for(int i=1; i <= n ; i++)
    {
        map<int,int>::iterator iter = students.find(i);
        
        //해당 학생이 map에 없으면 수업을 들을 수 있기에 answer후 순회
        if(iter == students.end()) {  answer++;  continue;}
        
        //해당 학생이 체육복이 없는경우
        if(iter->second == 0)
        {
            //자기 앞,뒷 번호의 여벌 체육복을 체크후 true인경우 해당 학생의 체육복을 1증가
           if(reserve_check(i-1,students) || reserve_check(i+1,students))  iter->second++; 
        }
        
        //해당 학생이 체육복이 1개 이상인경우 answer증가 
        if(iter->second >=1) {  answer++; } 
        
    }

    
    
    return answer;
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV2.가장 큰 수  (0) 2023.11.03
LV1.둘만의 암호  (0) 2023.10.26
LV2.택배상자  (0) 2023.10.26
LV2.롤케이크 자르기  (0) 2023.10.26
LV2.숫자 변환하기  (0) 2023.10.26

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제 내용 :

1.택배차가 원하는 택배번호 순서대로 택배를 실어야 함(인자값 : vector<int> order)

2.메인 컨테이너 벨트에 오름차순으로 택배가 존재하며,제일 앞에 택배만 꺼낼 수 있음

3.택배차에 짐을 실을때 원하는 택배가 아닌경우 서브 컨테이너 벨트에 택배를 보관할수 있음,

3-1.서브컨테이너 벨트는 제일 마지막에 쌓은 택배만 꺼낼 수 있음

4.두 벨트 에서 원하는 택배를 못 실을 경우 택배차는 출발함

이때 택배차에 몇개의 택배를 실을수 있는지 최대값을 찾는 문제 


풀이 : 

문제내용1 -> queue 자료구조에 적합 (순서대로 작업함에 따라 택배를 실고 삭제할때 앞에 원소 삭제하기가 용이함)

문제내용2 -> queue 자료구조에 적합

문제내용3 -> stack 자료구조에 적합

 

택배차의 queue<int> order를 순회하며 메인벨트(queue)와 서브벨트(stack)을 체크하며 answer의 값을 증가,

두 벨트에서 짐을 다 못 실을 경우 서브벨트에 택배를 쌓는다.

만약 메인벨트에서 더이상 서브벨트로 넣을 택배가 없으면  반복문에서 탈출하여 그대로 answer를 반환


코드 영역

더보기
#include <string>
#include <vector>
#include <queue>
#include <stack>

using namespace std;

//1.메인벨트와  적재 박스 비교
//2.같은경우 벨트와 적재박스 큐에서 제외 후 ,answer증가
//3.다른경우 서브벨트 top과 비교해서 같은경우 stack과queue에서 제외후 answer증가
//3.둘다 다른경우 서브벨트에 적재

int solution(vector<int> order) {
    int answer = 0;

    queue<int> q_order; // 기사님이 원하는 택배 순서
    queue<int> main_belt;// 메인 컨테이너벨트
    stack<int> sub_belt; // 서브 컨테이너벨트
    
    //택배차의 원하는 택배순서를 queue로 저장
    for (int i = 0; i < order.size(); i++)
    {
        main_belt.push(i + 1); 
        q_order.push(order[i]); 
    }

    while (!q_order.empty())
    {
        //메인 벨트의 택배와 택배차의 order list와 비교
        if (!main_belt.empty() && q_order.front() == main_belt.front())
        {
            answer++;
            q_order.pop();
            main_belt.pop();
        }
        //서브 벨트의 택배와 택배차의 order list와 비교
        else if (!sub_belt.empty() && q_order.front() == sub_belt.top())
        {
            answer++;
            q_order.pop();
            sub_belt.pop();
        }
        //메인 벨트의 서브 벨트 둘다 택배가 없는경우
        else
        {
            //메인 벨트에 택배가 있으면 서브벨트에 해당 택배를 보관 후 다시 순회
            if (!main_belt.empty())
            {
                sub_belt.push(main_belt.front());
                main_belt.pop();
            }
            //메인 벨트에 택배가 없으면 더이상 할 수 없으므로 탈출후 반환
            else { return answer; }
        }

    }

    return answer;
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV1.둘만의 암호  (0) 2023.10.26
LV1.체육복  (0) 2023.10.26
LV2.롤케이크 자르기  (0) 2023.10.26
LV2.숫자 변환하기  (0) 2023.10.26
LV2.2개 이하로 다른 비트  (0) 2023.10.26

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


문제 내용 : 두 사람한테 [토핑 종류의 갯수가 똑같이 분배되게 하는 방법의 수]를 찾는 문제

ex) A : 1 ,2 ,4  / B : 2, 3 ,4 => 토핑 종류는 다르지만 토핑의 종류의 갯수가 같으므로 맞는 경우

ex) A : 1,1,2,3, / B : 1,2,2,2,3 =>토핑 종류 갯수 같음

 

우선 토핑 갯수 및 종류에 상관 없기에 중복을 허용하지 않으면 레드-블랙트리 기반으로하는 map 자료 구조를 사용하였다.

map<int,int> // 토핑종류 , 토핑 갯수

 


첫번째 접근 방법 : A한테 n개 ,B한테 [전체 - n개] 나눠주기

지문 그대로 나눠주고 그 결과가 맞는지를 체크하기로 했다.
하지만 이렇게 되면 topping을 순회하는 반복문 1개 ,반복문 안에서 나눠주는 반복문이 2개로 loop가 토핑갯수 하나마다 loop수가 제곱이되는 상황이 발생이 되어 시간복잡도에 문제가 발생


두번째 접근 방법 : A한테 topping을 다 몰아준 후 , 반복문이 한번 loop할때마다 B한테 하나씩 주기

이렇게 되면 인자값으로 주는 토핑 배열 길이만큼만 순회하면 모든 방법을 알 수 있다.


코드 영역

더보기
#include <string>
#include <vector>
#include <algorithm>
#include <map>

using namespace std;

int solution(vector<int> topping) {
    int answer = 0;
    
    //A라는 사람의 topping map
    map<int,int>  map_A;
    //A의 빵에 모든 토핑을 올려놓기
    for (int elements : topping)
    {
        map<int, int> ::iterator iter = map_A.find(elements);

        if (iter != map_A.end()) iter->second++;
        else  map_A.insert({ elements,1 });
    }
    
     //A라는 사람의 topping map
    map<int, int> map_B;
    
     //토핑의 갯수만큼 순회를 하며 A의 토핑을 B한테 하나씩 주며 체크하기
    for (int i = 0; i < topping.size(); i++)
    {
        int pivot = topping[i];
 
        //map_A의 원소 제거 (B한테 하나 주기)
        map<int, int> ::iterator iter = map_A.find(pivot);
        if (iter != map_A.end())
        {
            iter->second--;
            if (iter->second <= 0) map_A.erase(iter);
        }

        //map_B의 원소 추가 (A한테서 받은 토핑 추가하기)
        iter = map_B.find(pivot);
        if (iter != map_B.end()) iter->second++;
        else  map_B.insert({ pivot,1 });

        //각 토핑의 map갯수가 같은지 체크 
        if (map_A.size() == map_B.size()) answer++;
    }

    return answer;
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV1.체육복  (0) 2023.10.26
LV2.택배상자  (0) 2023.10.26
LV2.숫자 변환하기  (0) 2023.10.26
LV2.2개 이하로 다른 비트  (0) 2023.10.26
LV.1 같은 숫자는 싫어  (0) 2023.06.09

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


첫번째 접근 방법 : bfs(재귀)로 접근

문제에서 원하는건 [ 최소 연산 횟수를 return ]이기 때문에 bfs로 접근하였고, bfs를 사용하기에 queue 자료구조를 사용하였다, 인자값이 작아서 경우의수가 적을경우에는 원하는 값이 나왓지만 인자값이 엄청 큰 경우 재귀함수 반복횟수 초과 및 시간 복잡도 문제가 발생했다.

 

재귀함수 장/단점 간단 정리

더보기

[재귀함수 장점] -> 가독성 Up

1.변수의 사용을 줄일 수 있다.

2.알고리즘 자체가 재귀함수에 어울리는 경우 ex)Tree 

[재귀함수 단점] -> 시간증가 , 스택오버플로우 발생 

1.함수를 호출이 반복적으로 일어나기에 시간이 반복문보다 더 걸릴 수 있다.

2.함수 호출시마다 스택영역에 메모리가 쌓여서 결국 스택 오버 플로우 발생이 된다.

 

두번째 접근 방법 : DP로 접근하기

인자값이 크면 클수록 경우의수가 너무 많아져서 DP를 사용

x에 3가지 방법의 계산 후 계산값을 저장할 배열이 필요하므로 [ y +1 ] 만큼 배열을 만든뒤 -1로 채운다.

-1로 채운 이유는 해당 index에 접근 안했다는 표시와 y값을 못구하는경우 -1을 return하기 위해서다.

각각의 방법으로 구한 계산 값을 index로 취급하고 , 배열[index]에 계산한 횟수를 저장한다.

 

ex) x=10 , y=40 ,n=5 인경우

dp[10] dp[11] dp[12] dp[13] dp[14] dp[15] ... dp[20] ... dp[30]
0(Start) -1 -1 -1 -1 1(10+5)   1(10*2)   1(10*3)

이런식으로 dp에 저장한다. 그리고 연산 중에 겹치는 값이 있을텐데  최소의 연산 횟수를 구하는것이기에

저장하려는 계산값과 저장되있는 계산값중 최소인것을 선택하여 저장하게하면된다.(최소 연산 횟수가 아닌경우 할필요가 없어짐)

 

이렇게 배열의 길이만큼 순회하면 원하는 값을 도출 할 수 있다.

 

코드영역

더보기
#include <string>
#include <vector>
#include <array>
#include <algorithm>
#include<cmath>
using namespace std;

int solution(int x, int y, int n) 
{
    int answer = 0;

    vector<int> dp;
    for (int i = 0; i < y + 1; i++) { dp.push_back(-1); }//dp 배열 data값을 전부 -1 초기화

    dp[x] = 0; // 초기 시작값 설정
   
    for (int i = x; i < y + 1; i++)
    {
        //해당 dp[i]에는 접근하지 않았기에 넘어감
        if (dp[i] == -1) continue;

        if (i * 2 <= y)
        {
            //계산한 값이 -1이면 접근하지 않았기에 바로 dp에 계산값을 초기화
            if (dp[i * 2] == -1) { dp[i * 2] = dp[i] + 1; }//해당 dp자리에 연산 횟수data가 있는경우 최소값을 골라서 초기화
            else { dp[i * 2] = min(dp[i] + 1, dp[i * 2]); }
        }

        if (i * 3 <= y)
        {
            if (dp[i * 3] == -1) { dp[i * 3] = dp[i] + 1; }
            else { dp[i * 3] = min(dp[i] + 1, dp[i * 3]); }
        }

        if (i + n <= y)
        {
            if (dp[i + n] == -1) { dp[i + n] = dp[i] + 1; }
            else { dp[i + n] = min(dp[i] + 1, dp[i + n]); }

        }
    }
    //dp배열 길이만큼 순회 후 dp[y]에 도달한 연산 횟수 값을 반환 
    return dp[y];
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV2.택배상자  (0) 2023.10.26
LV2.롤케이크 자르기  (0) 2023.10.26
LV2.2개 이하로 다른 비트  (0) 2023.10.26
LV.1 같은 숫자는 싫어  (0) 2023.06.09
LV.2 최솟값 만들기  (0) 2023.06.09

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

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr


첫번째 접근 방법 :  비트연산(XOR) 접근
비트 연산 문제로 파악하여 비트를 쉽게 파악하기 위해 bitset을 사용
2개 이하 다른 비트를 찾기 위해 XOR 비트연산을 이용하여 [1의 갯수 < 2]의 조건을 찾아 정답을 찾음
하지만 인자값으로 큰 숫자가 들어올 경우 XOR을 해야되는 연산 수가 늘어나며 시간 복잡도에서 문제가 발생

두번째 접근 방법 : 각 비트의 규칙을 찾음

2진 숫자(numbers) 2진 변환(answer) 처음보는 0 뒤의 1숫자 index numbers에 더해주는 값
010(2) 011(3) 0(처음 보는 0 뒤에 1이 없음) +2^0
011(3) 101(5) 1(0의 index는 두번째,2-1 =1) +2^1
100(4) 101(5) 0(처음 보는 0 뒤에 1이 없음) +2^0
101(5) 110(6) 0(0의 index는 첫번째, 1-1=0) +2^0
110(6) 111(7) 0(처음 보는 0 뒤에 1이 없음) +2^0
111(7) 1011(11) 2(0의 index는 세번째 3-1=2) +2^2

규칙을 보면 numbers의 비트들에서 오른쪽에서 처음으로 보는 0 뒤의 1숫자의 index를 2제곱해준 값을
numbers에 더해주면된다. 그렇게하면 비트는 무조건 2이하로 바뀌게 되는 조건을 만족한다.

 

ex)1 010(2)의 [처음보는 0뒤에 1 index]는 0이고, 그 값을 2^n에 대입하면

010 + 2^0 = 011(3) 값이 나온다.

ex2)011(3)의  [처음보는 0뒤에 1 index]는 1이고, 그 값을 2^n에 대입하면

011 + 2^1 = 101(5)  값이 나온다.


코드영역

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

using namespace std;

vector<long long> solution(vector<long long> numbers) {
    vector<long long> answer;
    
    for(long long i =0; i < numbers.size() ; i++)
    {
        long long number = numbers[i];
        long long index_0 = 0; //처음보는 0의 index
        
        //number를 이진법으로 변환하며 처음 만나는 0 찾기
        while(number > 0)
        {
            if(number % 2 == 0) { break; } // 해당 이진법의 자리가 0인경우 탈출
            index_0++; //0이 아닌경우 index가 증가함
            number *= 0.5;
        }

        //0뒤의 숫자1의 index를 알아야하기 때문에 -1을 계산
        if(index_0 > 0) { index_0 -= 1;} 

        //number에 더해줄 2^count 를 구함
        long long sum = 1;
        for(long long i =0; i< index_0; i++) {  sum *= 2;}
        
        answer.push_back(numbers[i] + sum);
    }
    return answer;
}

'코딩테스트 > 프로그래머스' 카테고리의 다른 글

LV2.롤케이크 자르기  (0) 2023.10.26
LV2.숫자 변환하기  (0) 2023.10.26
LV.1 같은 숫자는 싫어  (0) 2023.06.09
LV.2 최솟값 만들기  (0) 2023.06.09
LV.1 내적  (0) 2023.06.09

절두체 컬링(Frustum Culing)이란?

카메라의 시야(절두체)에서 들어오지 않은 객체를 제외하는 것,

즉, 그 객체들은 렌더링에서 제외된다. 하지만 제외작업을 수행하기 위한 연산작업은 필요로 한다.

 

World Space에 배치된 Mesh(객체)들을 <View 변환> 하는 과정

절두체 컬링을 하기 위해서는 World Space에 있는 절두체 영역을(a그림) 구해야된다.

최종 절두체 영역(c그림)의 8개 정점에서 역으로 계산을 해야한다. 

 

Vworld = VLocal * TM(Transfrom Matrix)  :  Vertex data를 월드변환하는 과정의 식

최종Vertex = Vworld * ViewMatrix * ProjectMatix : 월드로 배치된 Vertex를 View(카메라)행렬과 투영행렬을 곱해 카메라를 원점으로 Vertex가 배치되는 식이다.

 

최종Vertex = Vworld * ViewMatrix * ProjectMatirx 식 양변에 카메라 * 투영 역행렬를 곱해주면

최종 Vertex * (카메라 * 투영 역행렬) = Vworld가 나오게 된다. 

( ∵  카메라 * 투영 역행렬 : (ViewMatrix * ProjectMatix)^-1 )

이러면 WorldSpace에 있는 절두체의 영역(우리가 보고있는 카메라 화면)을 찾을수있게된다. 


필요한 구성요소는

절두체 컬링을 하기위해 절두체 class가 필요함 -> Frustum class 를 생성

View행렬과 Matrix행렬을 가져오기 위한 Camera -> Camera 포인터 변수 필요

절두체 면(Plane) 과 벡터의 수직상 거리를 비교할때 필요한 오차범위 -> planeEpsilon;

절두체 면을 관리할 vector -> frustumPlane[6] //밑에 그림의 숫자와 index와 일치

 

8개의 veretx로 6개의 plane을 만들어 절두체 영역을 만들것, 코드영역에서도 이렇게 표현될 예정

Init함수 , 최종Vertex를 구성하는 함수, Frustum 내부에 객체가 있는지 check하는 함수 등등으로 구성하면 될것이다.

 

<Frusutum class> :

벡터(객체의 원점)를 인자값으로 가져와 Frustum 영역의 내부,외부를 체크하여 외부에 있다고 판단시 절두체컬링을 하게하는 class다.

더보기
//Frustum.h

#pragma once
#include "Components/DXCamera.h"

class Frustum
{
private:
	DXCamera* camera; //view,Projection 행렬을 가져오기 위한 변수 

	float planeEpsilon; // 평면테스트에서 사용되는 부동 소수점 오차범위를 나타냄 
	XMVECTOR frustumPlane[6]; //Plane : 평면 -> 절두체 평면 

public:
	bool Initialize(DXCamera* camera, float planeEpsilon = FLT_EPSILON);
	void ConstructFrustum();

	//IsInFrustumBounds~ 함수 설명
    //물체의 기준으로 히트박스(Bounds뒤에 들어가는 도형,도형은 자유) 만들어서 그 안에있으면 있따고 처리 체크 

	bool IsInFrustum(XMFLOAT3 v); //Frustum 안에 물체 확인 bool 변수
	bool IsInFrustum(XMVECTOR v); //Frustum 안에 물체 확인 bool 변수
	bool IsInFrustumExceptUpDown(XMFLOAT3 v); //Frustum의 윗면과 아랫면은 무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수

	bool IsInFrustumBoundsSphere(XMFLOAT3 v,float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
	bool IsInFrustumBoundsSphere(XMVECTOR v, float radius); //Frustum 안에 물체의 기준으로 구 모형 안에 있는지 물체 확인 bool 변수
	bool IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius); //Frustum안에 물체의 기준으로 구 모혀 위와 아래는무조건 있는걸로 취급하고 좌우앞뒤만 체크하는 변수

	bool IsInFrustumBoundsCube(XMFLOAT3 v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수
	bool IsInFrustumBoundsCube(XMVECTOR v, float radius); //Frustum 안에 육면체 모양 안에 물체 확인 bool 변수

	//Rectangle : AABB/OBB -> 2D게임 만들때거나 3D게임의 UI 만들때 사용
	bool IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수
	bool IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r); //Frustum 안에 2D사각형 안에 물체 확인 bool 변수

private:
	bool IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r);
};

//====================================================================

#include "Frustum.h"
bool Frustum::Initialize(DXCamera* camera, float planeEpsilon)
{
	if (!camera) return false; // 매개변수 camera 가 존재하지 않으면 false 반환

	//Frustum data 초기화
	this->camera = camera;
	this->planeEpsilon = planeEpsilon;
	for (int i = 0; 6 > i; i++) frustumPlane[i] = XMVectorSet(0, 0, 0, 0);
	return true;
}

void Frustum::ConstructFrustum()
{
	//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
	//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
    XMFLOAT3 frustumVertices[8] =
	{
		{-1.0f, -1.0f, 0.0f }, // near left bottom v0
		{1.0f, -1.0f, 0.0f }, // near right bottom v1
		{1.0f, -1.0f, 1.0f }, // far right bottom v2
		{-1.0f, -1.0f, 1.0f }, // far left bottom v3
		{-1.0f, 1.0f, 0.0f }, // near left top v4
		{1.0f, 1.0f, 0.0f }, // near right top v5
		{1.0f, 1.0f, 1.0f }, // far right top v6
		{-1.0f, 1.0f, 1.0f }, // far left top v7
	};

	//view * proj의 역행렬 계산.
	XMMATRIX invViewProjectionMatrix = XMMatrixInverse(NULL, camera->GetViewMatrix() * camera->GetProjectionMatrix());
	
	XMVECTOR frustumVectors[8]; //최종vertex * (viewprojectionMatrix)T를 계산한 vertex들의 vector
	for (int i = 0; 8 > i; i++)
	{
		// XMVector3TransformCoord : 주어진 행렬을 사용하여 벡터로 변형하는 함수
		//최종vertex * (viewprojectionMatrix)T 계산,
		frustumVectors[i] = XMVector3TransformCoord(XMVectorSet(frustumVertices[i].x, frustumVertices[i].y, frustumVertices[i].z, 1.0f),
			invViewProjectionMatrix);
	}
	
    //계산된 Vworld(월드상의 vertex)들을 3개씩 사용하여 절두체의 평면을 구성 
    //인덱스 순서에 따라 절두체면(폴리곤)의 윗면이 결정되어 윗면이 법선벡터의 방향이다.(중요)
	frustumPlane[0] = XMPlaneFromPoints(frustumVectors[4], frustumVectors[7], frustumVectors[6]); // 상 평면(top)
	frustumPlane[1] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[1], frustumVectors[2]); // 하 평면(bottom)
	frustumPlane[2] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[4], frustumVectors[5]); // 근 평면(near)
	frustumPlane[3] = XMPlaneFromPoints(frustumVectors[2], frustumVectors[6], frustumVectors[7]); // 원 평면(far)
	frustumPlane[4] = XMPlaneFromPoints(frustumVectors[0], frustumVectors[3], frustumVectors[7]); // 좌 평면(left)
	frustumPlane[5] = XMPlaneFromPoints(frustumVectors[1], frustumVectors[5], frustumVectors[6]); // 우 평면(right)
}


bool Frustum::IsInFrustum(XMFLOAT3 v)
{
	return IsInFrustum(XMVectorSet(v.x, v.y, v.z, 1.0f));
}
bool Frustum::IsInFrustum(XMVECTOR v)
{
	return IsInFrustumBoundsSphere(v, 0.0f);
}
bool Frustum::IsInFrustumExceptUpDown(XMFLOAT3 v)
{
	return IsInFrustumBoundsSphereExceptUpDown(v, 0.0f);
}
bool Frustum::IsInFrustumBoundsSphere(XMFLOAT3 v, float radius)
{
	return IsInFrustumBoundsSphere(XMVectorSet(v.x, v.y, v.z, 1.f), radius);
}

//절두체 안에 경계구가 있는지 체크하는 함수( 작동방식 밑에 서술)
bool Frustum::IsInFrustumBoundsSphere(XMVECTOR v, float radius)
{
	for (int i = 0; 6 > i; i++) // 왜 6인가? 절두체의 면이 6개임
	{
		//XMPlaneDotCoord의 함수 반환값은 XMVECTOR인데, XMVECTOR의 모든값을 내적크기로 반환한다.
		//VectorGetX,Y,Z 함수 아무거나 사용해도 괜찮다. 전부 내적의 값이기 떄문이다.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
		{
			// 벡터의 내적(물체와 절두체 면의 수직상의 거리) > 물체의 경계(히트박스)거리 가 참인경우
			return false; //frustum 영역 외부에 있음
		}
	}

	return true;//frustum 영역 내부에 있음
}

bool Frustum:: IsInFrustumBoundsSphereExceptUpDown(XMFLOAT3 v, float radius)
{
	XMVECTOR vector = XMVectorSet(v.x, v.y, v.z, 1.0f);

	for (int i = 2; 6 > i ; i++) // 0,1 인덱스의 vertex는 윗면과 ,아랫면의 vertex이므로 제외
	{
		//XMPlaneDotCoord (평면(plane) , 벡터(vertex) ) : 평면과 벡터사이의 내적값을 반환.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], vector)) > (radius + planeEpsilon)) return false;
	}

	return true;
}

//절두체를 경계박스로 컬링하는 함수 
bool Frustum::IsInFrustumBoundsCube(XMFLOAT3 v, float radius)
{
	for (int i = 0; 6 > i; i++)
	{
		if (IsInBoundsCube(frustumPlane[i], v, XMFLOAT3(radius, radius, radius))) continue;
		return false;
	}
	return true;
}

bool Frustum::IsInFrustumBoundsCube(XMVECTOR v, float radius)
{
	XMFLOAT3 dest;
	XMStoreFloat3(&dest, v);
	return IsInFrustumBoundsCube(dest, radius);
}

//절두체를 2D사각형으로 컬링하는 함수(2D게임에서 사용하게됨)
bool Frustum::IsInFrustumBoundsRectangle(XMFLOAT3 v, XMFLOAT3 r)
{
	for (int i = 0; 6 > i; i++)
	{
    	//
		if (IsInBoundsCube(frustumPlane[i], v, r)) continue;
		return false;
	}
	return true;
}

bool Frustum::IsInFrustumBoundsRectangle(XMVECTOR v, XMVECTOR r)
{
	XMFLOAT3 destV, destR;
	XMStoreFloat3(&destV, v);
	XMStoreFloat3(&destR, r);
	return IsInFrustumBoundsRectangle(destV, destR);
}

//경계박스 ,및 경계사각형(2D)의 함수 작동방식 
bool Frustum::IsInBoundsCube(XMVECTOR p, XMFLOAT3 v, XMFLOAT3 r)
{
	return
		(
        	//절두체 면(p) ,벡터v,r연산
            //모든 벡터의 내적이 오차범위 보다 작으면 있면 frustum 안에 존재 
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x - r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y - r.y, v.z + r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z - r.z, 1.0f))) <= planeEpsilon) ||
			(XMVectorGetX(XMPlaneDotCoord(p, XMVectorSet(v.x + r.x, v.y + r.y, v.z + r.z, 1.0f))) <= planeEpsilon)
		);
}

 

최종 Vertex의 값 범위가 (-1,-1,0) ~ ( 1,1,1) 사이의 값으로 바뀐다 라는 주석이 있는데

이 값은 D3D에서는 절대 고정이며 view 변환시 카메라 시점을 원점으로 바꾸고 투영행렬을 계산하여 이미지화 (3D->2D)화 시키기 때문에 그런것이다.

//최종 vertex는 (-1,-1,0) ~ (1,1,1) 사이의 값으로 바뀐다.
	//최종Vertex = Vworld * ViewMatrix * ProjectMatirx해서 나온 Frusutm 영역의 8개 vertex data임.
    XMFLOAT3 frustumVertices[8] =
	{
		{-1.0f, -1.0f, 0.0f }, // near left bottom v0
		{1.0f, -1.0f, 0.0f }, // near right bottom v1
		{1.0f, -1.0f, 1.0f }, // far right bottom v2
		{-1.0f, -1.0f, 1.0f }, // far left bottom v3
		{-1.0f, 1.0f, 0.0f }, // near left top v4
		{1.0f, 1.0f, 0.0f }, // near right top v5
		{1.0f, 1.0f, 1.0f }, // far right top v6
		{-1.0f, 1.0f, 1.0f }, // far left top v7
	};

 

 

 

 

절두체 각 면의 법선벡터 방향을 정하는 것은

그래픽스 파이프라인 폴리곤을 그리는 방식에 따라 정해진다.

이 블로그의 그래픽스 파이프라인 - 기본용어 정리 - Vertex를 이용하여 삼각형을 그릴때 필요한 개념 내용을 참고하면 좋다.

https://01149.tistory.com/4

 

기본용어 정리

글 작성자가 공부하고 배운것을 간략하게 정리하는 페이지 입니다. 필요한 정보가 있으시면 확인하시고 만약 잘못된 내용이 있거나 이런 내용이 추가됬으면 좋겠다라면 댓글 부탁드립니다. 감

01149.tistory.com

 

절두체 컬링의 경계 구 방식의 작동방식을 설명 할 것이다.

bool Frustum::IsInFrustumBoundsSphere(XMVECTOR v, float radius)
{
	for (int i = 0; 6 > i; i++) // 왜 6인가? 절두체의 면이 6개임
	{
		//내적은 frustumPlane에서 v까지의 수직상 거리와 같다.
		//벡터의 내적의 크기(벡터 * 법선벡터 = 해당면으로 수직의 벡터크기(길이) 
		// {벡터의 내적 < 오브젝트의 구 범위(반지름이 더 크면)} frustum안에 있다는 뜻이다.
		// XMPlaneDotCoord의 함수 반환값은 XMVECTOR인데, XMVECTOR의 모든값을 내적크기로 반환하기 떄문이다.
		// VectorGetX,Y,Z 함수 아무거나 사용해도 괜찮다. 전부 내적의 값이기 떄문이다.
		if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
		{
			// 벡터의 내적(물체와 절두체 면의 수직상의 거리) > 물체의 경계(히트박스)거리 가 참인경우
			return false; //frustum 영역 외부에 있음
		}
	}

	return true;//frustum 영역 내부에 있음
}
절두체 컬링 - 경계구 방식의 작동 방식

위의 그림 과정을 절두체 면이 6개니 총 6번 반복하여 동작을한다.

if (XMVectorGetX(XMPlaneDotCoord(frustumPlane[i], v)) > (radius + planeEpsilon))
{
    return false;
}

반복문 동작중 절두체 6면 중 한 면이라도,

벡터의 내적(수직상의 거리) 이 radius + planeEpsilon (경계 구 반지름 + 오차범위)보다 길면
frustun 외부에 존재하는 것이기 때문에 절두체 컬링(화면에서 렌더링을 제외하는)을 작업하게 된다.

<DirectX11Graphics> : DriectX 11 기반으로 Scene을 렌더링하기 위한 그래픽스 객체

더보기
// DirectX11Graphics.h
#include "Frustum.h"
class DirectX11Graphics final : public Graphics
{
Private:

	// Mesh의 이미지 관련 함수들을 담당하고 있음
    std::unique_ptr <PrimitiveModel> primitive; 
    // 컴포넌트 구조의 Mesh Obj : 컴포턴트 추가 및 Update관련(Mesh의 크기,회전,이동 함수등) 
	DXGameObject primitiveObj; 

	Frustum frustum; //절두체컬링을 위한 절두체 객체 추가
};

//========================================================

// DirectX11Graphics.cpp

void DirectX11Graphics::RenderFrame() //그래픽스 객체의 Draw 함수(프레임마다 렌더링함수)
{
	//렌더링 할때마다 , 카메라시점 기준(뷰행렬,투영행렬)으로 절두체 영역 생성
	frustum.ConstructFrustum();

    // GameObject Primitive Mesh Draw
    //절두체 컬링 - 경계구 판정을 사용하여 Mesh의 위치vector와 크기Vector를 인자값으로 가져감
    //크기vector는 경계구의 반지름으로 사용, 해당 객체가 카메라영역에서 사라지고 경계구까지 영역에서 벗어나게되면 절두체 컬링진행
   	//(이 자리에 경계 구의 반지름 값을 조정하여 넣을 수있음 )
    if (frustum.IsInFrustumBoundsSphere(primitiveObj.GetTransform()->GetPosition(), XMVectorGetX(primitiveObj.GetTransform()->GetScale())/*bounds*/))
    {
    	//절두체 6면, 영역 내에 Mesh(객체)가 존재 함으로 Draw를 한다.
    	primitiveObj.Draw(cameraObj.GetComponent<DXCamera>()->GetViewProjectionMatrix());
	}
}


bool DirectX11Graphics::InitializeScene() // 그래픽스 객체의 Scene(장면) Init
{
	//상자 mesh
    Primitive = std::make_unique<PrimitiveCube>(); // 상자 모양 mesh
    Primitive->Initialize(device.Get(), deviceContext.Get(), constantMatricesBuffer);
    Primitive->MakePrimitive("Textures\\box.jpg"); // mesh에 텍스쳐 이미지 추가
    PrimitiveObj.AddComponent<DXMeshRenderer>()->SetModel(Primitive.get());
    
    //절두체 객체 Init , 카메라의 뷰행렬,투영행렬이 필요하기 때문 -> 역행렬을 구해야함
	frustum.Initialize(cameraObj.GetComponent<DXCamera>());

}

 

D3D 공부하던 코드를 긁어온거입니다. 코드 보실때 참고만해주세요 


3D 공간에서 기존 카메라의 영향을 받지 않는 2D Obj를 만든다.

즉, D3D를 이용하여 2D 게임을 만들거나, 게임의 UI를 구성할때 많이 사용된다.

 

Sprite2D의 구성요소는 

유저에게 보여줘야할 2D Obj -> sprite

2D를 비춰줄 전용 카메라 Obj -> camera2D

2D를 이미지를 렌더링해줄 컴포넌트 ->SpriteRenederer

2D에 사용할 전용 VS셰이더와 PS셰이더 -> sprite_vertexShader, sprite_pixelShader

셰이더를 사용하면 필요한 HLSL -> sprite2D_ps.hlsl,sprite2D_vs.hlsl (따로작성)

 

<HLSL 작성>

더보기
//sprite2D.ps.hlsl

struct PixelInput
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
}; //빛의 영향을 받지 않기 때문에 법선벡터 data를 가질필요가 없다.

Texture2D tex : TEXCOORD: register(t0);
SamplerState sample : SAMPLER: register(s0);
float4 main(PixelInput input) : SV_TARGET
{
	return input.color * tex.Sample(sample, input.texcoord);
}

//======================================================

// sprite2D.vs.hlsl

#pragma pack_matrix(row_major)
cbuffer constants
{
	matrix worldMatrix;
	matrix viewProjectionMatrix;
};
struct VertexInput
{
	float3 position : POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
	float3 normal : NORMAL;
};
struct VertexOutput
{
	float4 position : SV_POSITION;
	float4 color : COLOR;
	float2 texcoord : TEXCOORD0;
}; // ps.hlsl의 input과 vs.hlsl의 output은 똑같아야한다.(중요)

VertexOutput main(VertexInput input)
{
	VertexOutput output;
	output.position = mul(float4(input.position, 1.0f), worldMatrix);
	output.position = mul(output.position, viewProjectionMatrix);
	output.color = input.color;
	output.texcoord = input.texcoord;
	return output;
}

<SpriteRenderer 생성(컴포넌트)>

더보기
//SpriteRenderer class 생성 - 컴포넌트로 사용

// DXSpriteRenderer.h
#pragma once
#include "../DXGameObject.h"
#include "../Model.h"
class DXSpriteRenderer : public RenderComponent
{
    private:
        std::unique_ptr<Mesh> mesh; //그림을 그릴 mesh
        
        ID3D11DeviceContext* deviceContext;
        ConstantBuffer<Matrices>* vsConstantBuffer; //VertexShader에서 사용할 행렬data
    public:
        DXSpriteRenderer(DXGameObject* owner); // Render컴포넌트를 자기자신으로 선언 하는 생성자
        
        //sprite이미지를 로드해서 Mesh에 초기화
        void LoadSpriteImage(ID3D11Device* device, ID3D11DeviceContext* deviceContext, ConstantBuffer<Matrices>& vsConstantBuffer, const std::string& filename);
        
    protected:
    	virtual void Opertate() override; 
};

//=========================================

// DXSpriteRenderer.cpp

#include "DXSpriteRenderer.h"
DXSpriteRenderer::DXSpriteRenderer(DXGameObject* owner) :
    RenderComponent(owner),
    deviceContext(nullptr),
    vsConstantBuffer(nullptr)
{
}


void DXSpriteRenderer::LoadSpriteImage(ID3D11Device* device, ID3D11DeviceContext* deviceContext, ConstantBuffer<Matrices>& vsConstantBuffer, const std::string& filename)
{
	//2DspriteRenderer의 deviceContext,vsConstantBuffer 초기화
	this->deviceContext, = deviceContext;
	this->vsConstantBuffer = &vsConstantBuffer;
	
    //vertex 벡터 초기화
	std::vector<ModelVertex> vertices =
	{
		ModelVertex(-0.5f, -0.5f, 0.0f,0.0f, 0.0f), // top left
		ModelVertex(0.5f, -0.5f, 0.0f,1.0f, 0.0f), // top right
		ModelVertex(-0.5f, 0.5f, 0.0f,0.0f, 1.0f), // bottom left
		ModelVertex(0.5f, 0.5f, 0.0f,1.0f, 1.0f), // bottom right
	};
    //index 벡터 초기화 
	std::vector<DWORD> indices =
	{
		0, 1, 2,
		2, 1, 3
	};
    
    //인자값으로 가져온 텍스쳐 파일이름을 Texture생성자에서 2D이미지 로드
	std::vector<Texture> textures = { Texture(device, filename) };
	//mesh에 data를 초기화해서 모델링하는 작업 
    mesh = std::make_unique<Mesh>(device, deviceContext, vertices, indices, textures, XMMatrixIdentity());
}

//constantBuffer = 정점 및 픽셀 셰이더에서 사용될 상수를 모아 놓은 버퍼이다.
//ConstantBuffer에 월드행렬과 뷰투영 행렬을 초기화 하고 메모리락(ApplyChanges) 한 뒤 mesh를 Draw한다. 
void DXSpriteRenderer::Opertate()
{	
	deviceContext->VSSetConstantBuffers(0, 1, vsConstantBuffer->GetAddressOf());
	vsConstantBuffer->data.world = mesh->GetTransformMatrix() * owner->GetTransform()->GetWorldMatrix();
	vsConstantBuffer->data.viewProjection = viewProjectionMatrix;
	vsConstantBuffer->ApplyChanges();
	mesh->Draw();
}

<DirectX 11 Graphics 의 2D sprite 추가>

: DirectX 11 Graphics : DirectX 11 기반으로 씬(Scnen)을 렌더링하기 위한 그래픽스 객체

더보기
// DirectX11Graphics.h

#include "Components/DXSpriteRenderer.h" //SpriteReneder 헤더 추가
class DirectX11Graphics final : public Graphics
{
Private:
	...
    VertexShader sprite_vertexShader; //2Dsprite VS
    PixelShader sprite_pixelShader; //2Dsprite PS
    DXGameObject camera2D; //2D 전용 카메라 Obj
    DXGameObject sprite; //2D 이미지 mesh
	...
};

//=================================

// DirectX11Graphics.cpp

void DirectX11Graphics::RenderFrame()
{
	...
    //deviceContext에 sprite관련 data를 Set(IA,VS,PS)
    deviceContext->IASetInputLayout(sprite_vertexShader.GetInputLayout());
    deviceContext->VSSetShader(sprite_vertexShader.GetShader(), NULL, 0);
    deviceContext->PSSetShader(sprite_pixelShader.GetShader(), NULL, 0);
    //Camera2D에 뷰투영행렬을 인자값으로 가져와서 draw를 실행
    sprite.Draw(camera2D.GetComponent<DXCamera>()->GetViewProjectionMatrix());
    swapChain->Present(NULL, NULL);
}

//Shader Init
bool DirectX11Graphics::InitializeShader()
{
   ...
    //vs,ps HLSL Initialize
    if (!sprite_vertexShader.Initialize(device.Get(), L"sprite2D.vs.hlsl", inputLayout,
    numElements)) return false;
    if (!sprite_pixelShader.Initialize(device.Get(), L"sprite2D.ps.hlsl")) return false;
    return true;
}

//Scene Init
bool DirectX11Graphics::InitializeScene()
{

...
		//Cam(Base컴포넌트)에 DXCamera(컴포넌트)추가
		DXCamera* cam = camera2D.AddComponent<DXCamera>();
        //Cam의 투영방법 Set : 직교 투영
		cam->SetProjectionType(ProjectionType::Orthographic);
		//카메라 좌표계 원점을 left,top에서 중앙으로 옮기는 과정 
		cam->SetProjection(screenWidth, screenHeight, -10.0f, 10.0f);

		//2D 이미지(sprite)dp Renderer 컴포넌트 추가후 이미지 로드
		sprite.AddComponent<DXSpriteRenderer>()->LoadSpriteImage(
			device.Get(), deviceContext.Get(), constantMatricesBuffer,
			"Textures\\hello_world_sprite.jpg");

		//2D이미지는 Scale 및 Translation 조정시 3D처럼 원본의 배율이 아닌 직접 값을 입력해야된다. 
		//sprite.GetTransform()->SetLocalScale({ 100.0f, 100.0f, 1 }); // Scale Set
		//sprite.GetTransform()->SetLocalRocation({ 0, 0, 30.0f }); // Rotation Set(Z축을 회전시켜야 2D이미지가 회전됨)
        //sprite.GetTransform()->SetTranslation({ 100.0f, 100.0f, 0 }); // Translation Set
        /**/


...
}

 

2D 카메라 세팅중 직교투영과 좌표계 수정이 필요하다.

bool DirectX11Graphics::InitializeScene()
{        ...
        //Cam의 투영방법 Set : 직교 투영
		cam->SetProjectionType(ProjectionType::Orthographic);
		//카메라 좌표계 원점을 left,top에서 중앙으로 옮기는 과정 
		cam->SetProjection(screenWidth, screenHeight, -10.0f, 10.0f);
        ...
}

 [2D카메라 투영 세팅]

//DXCamera 투영 행렬을 Set 하는 함수
void DXCamera::SetProjection(float width, float height, float nearZ, float farZ, float fovDegrees)
{
	this->width = width;
	this->height = height;
	this->nearZ = nearZ;
	this->farZ = farZ;
	this->fovDegrees = fovDegrees;

	switch (type)
	{ // 원근/직교(Perspective/Orthographic) 투영 행렬 생성.
	case ProjectionType::Perspective: projectionMatrix = XMMatrixPerspectiveFovLH(fovDegrees * Deg2Rad, (width / height), nearZ, farZ); break;
	case ProjectionType::Orthographic: projectionMatrix = XMMatrixOrthographicOffCenterLH(-width / 2, width / 2, height / 2, -height / 2, nearZ, farZ); break;
	}
}

DXCamera 객채 생성자에서 원근투영(Perspective)으로 set하게 해놨기 떄문에 직교투영(Orthographic)으로 set해줘야한다.

 

-원근투영

뷰투영행렬 연산을 거쳐 카메라 화면(유저가 보는 화면 = viwe port)이 모니터에 출력되는데

이때 데카르트 좌표계(정규좌표)를 사용하고 있다.

우리가 흔히 쓰는 데카르트 좌표계가 기준

 그렇기 때문에 원점(0,0)이 Width,Height 값만 있으면 원점은 정중앙으로 인식된다.

XMMatrixPerspectiveFovLH(fovDegrees * Deg2Rad, (width / height), nearZ, farZ); 

view Space의 각도(=카메라의 각도) , (카메라 화면 폭 / 카메라 화면 높이) , 가까운 Z축 plane , 멀리있는 Z축 plane

 

-직교투영

원근투영과는 다르게 직교투영은 화면좌표계(컴퓨터좌표계)를 사용한다.

원점(0,0)이 정중앙이 아닌 left,top으로 되있기 때문에 쉽게 사용하라면 원근투영처럼 원점을 정중앙으로 옮겨줘야 된다.

Y축의 증감 방향이 반대로 되어있다.

XMMatrixOrthographicOffCenterLH(-width / 2, width / 2, height / 2, -height / 2, nearZ, farZ);  

width와 height의을 절반으로 나눠 -,+값으로하면 원하는 카메라 화면을 구할수 있다.

 

 

+ Recent posts