https://www.acmicpc.net/problem/4949

 

우선 한줄의 입력을 받은 다음에 문자열을 순회하면서 단어일때는 제외하고 시작하는 괄호일때 ( 나 [ 를 stack에 각각 push해주고 비어있지않고 짝이 맞는 괄호가 있다면 pop을 해주고 비어있다면 순회를 종료한다. 이러한 처리를 통해 yes인지 no인지 출력해줄 수 있다.

 

정답코드

#include <iostream>
#include <stack>
#include <string>

using namespace std;

int main() {
    while (true) {
        string line;
        getline(cin, line);  // 한 줄 전체 입력

        if (line == ".") break;  // 종료 조건

        stack<char> s;
        bool isBalanced = true;  // 균형 여부 체크

        for (int i = 0; i < line.size(); i++) {
            if (line[i] == '(' || line[i] == '[') {
                s.push(line[i]);  // 여는 괄호를 스택에 넣음
            }
            else if (line[i] == ')') {
                if (!s.empty() && s.top() == '(') {
                    s.pop();  // 괄호가 짝이 맞으면 스택에서 제거
                }
                else {
                    isBalanced = false;  // 짝이 맞지 않으면 불균형
                    break;
                }
            }
            else if (line[i] == ']') {
                if (!s.empty() && s.top() == '[') {
                    s.pop();  // 대괄호가 짝이 맞으면 스택에서 제거
                }
                else {
                    isBalanced = false;
                    break;
                }
            }
        }

        if (!s.empty()) isBalanced = false;  // 스택이 비어 있지 않으면 불균형

        if (isBalanced) cout << "yes" << endl;
        else cout << "no" << endl;
    }

    return 0;
}

https://www.acmicpc.net/problem/9012

 

Stack을 사용해서 문자열을 순회하면서 짝이 맞으면 push pop을 통해 짝을 맞춰주고 아니라면 false를 return 해주는 함수를 통해 규칙을 검사해 주었다.

 

정답코드

#include <iostream>
#include <stack>

using namespace std;

// VPS 판별 함수
bool isVPS(string ps)
{
    stack<char> s;

    for (int i = 0; i < ps.size(); i++)
    {
        if (ps[i] == '(') {
            s.push('(');   // 여는 괄호일 때 스택에 추가
        }
        else {
            // 닫는 괄호일 때
            if (s.empty()) {
                return false;  // 닫는 괄호가 나왔는데 스택이 비어 있으면 잘못된 괄호열
            }
            else {
                s.pop();  // 괄호 짝을 맞추는 경우
            }
        }
    }

    // 모든 괄호를 처리한 후 스택이 비어 있어야 올바른 VPS
    return s.empty();
}

int main()
{
    int n;
    cin >> n;

    for (int i = 0; i < n; i++)
    {
        string ps;
        cin >> ps;
        if (isVPS(ps)) {
            cout << "YES" << endl;
        }
        else {
            cout << "NO" << endl;
        }
    }

    return 0;
}

https://www.acmicpc.net/problem/11478

 

 

처음에 봤을 때 부분 문자열의 조합을 이중반복문을 통해 구하고 이를 set 자료형을 통해 하나씩 넣고 마지막에 그 크기를 출력해주는 것으로 생각했다. 이렇게 풀어도 정답이 된다. 

 

정답코드1(반복문 사용)

#include <iostream>
#include <set>

using namespace std;

set<string> s;
int main()
{
	string word;

	cin >> word;

	//부분 문자열 시작위치
	for (int i = 0; i < word.size(); i++)
	{
		string w = "";
		for (int j = i; j < word.size(); j++)		//부분 문자열 길이
		{
			w += word[j];
			s.insert(w);
		}
	}

	cout << s.size() << endl;
}

 

하지만 더 최적화해보자면 접미사 배열 (Suffix Array) 이라는 것을 사용해서 풀면 된다. 

이때 접미사 배열은 문자열 s의 모든 접미사를 사전 순으로 정렬한 것으로 각 접미사가 문자열에서 시작하는 인덱스를 기준으로 정렬 한다. 예시 입력을 바탕으로 보자면 

s의 접미사 목록:

  1. s[0:] = "ababc"
  2. s[1:] = "babc"
  3. s[2:] = "abc"
  4. s[3:] = "bc"
  5. s[4:] = "c"

이런식으로 접미사가 만들어진다.

여기서 중복되는 부분 문자열을 제거하기 위해 접미사 배열을 활용한 LCP 배열이라는 것을 사용하게 되는데 이는 은 접미사 배열의 인접한 접미사들 간에 겹치는 접두사의 길이를 저장한 배열이다. 

인접한 접미사들 간의 공통 접두사:

  1. "ababc"와 "abc"의 공통 접두사: "ab" → 길이 2
  2. "abc"와 "babc"의 공통 접두사: 없음 → 길이 0
  3. "babc"와 "bc"의 공통 접두사: "b" → 길이 1
  4. "bc"와 "c"의 공통 접두사: 없음 → 길이 0

이렇게 되면 LCP배열은  [2, 0, 1, 0] 이런식으로 만들어 진다. 

이제 중복되는 것을 포함한 전체 부분 문자열의 개수를 구해준 다음 중복되는 수를 빼주면 된다. 

부분 문자열의 개수는 n(n+1) /2 로 계산할 수 있다. 

이를 코드로 구현하면 다음과 같다.

 

 

정답코드2(접미사 배열활용)

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

using namespace std;

// 접미사 배열 구하기
vector<int> build_suffix_array(const string& s) {
    int n = s.size();
    vector<int> suffix_array(n), rank(n), temp(n);

    // 초기 순위 설정 (문자의 아스키 값에 따른 정렬)
    for (int i = 0; i < n; i++) {
        suffix_array[i] = i;
        rank[i] = s[i];
    }

    // 접미사 길이 1, 2, 4, 8...씩 늘려가며 정렬
    for (int len = 1; len < n; len *= 2) {
        auto cmp = [&](int i, int j) {
            if (rank[i] != rank[j]) return rank[i] < rank[j];
            int ri = (i + len < n) ? rank[i + len] : -1;
            int rj = (j + len < n) ? rank[j + len] : -1;
            return ri < rj;
        };
        sort(suffix_array.begin(), suffix_array.end(), cmp);

        // 임시 순위 갱신
        temp[suffix_array[0]] = 0;
        for (int i = 1; i < n; i++) {
            temp[suffix_array[i]] = temp[suffix_array[i - 1]] + cmp(suffix_array[i - 1], suffix_array[i]);
        }
        rank = temp;
    }
    return suffix_array;
}

// LCP 배열 구하기
vector<int> build_lcp_array(const string& s, const vector<int>& suffix_array) {
    int n = s.size();
    vector<int> rank(n), lcp(n);

    for (int i = 0; i < n; i++) {
        rank[suffix_array[i]] = i;
    }

    int h = 0;
    for (int i = 0; i < n; i++) {
        if (rank[i] > 0) {
            int j = suffix_array[rank[i] - 1];
            while (i + h < n && j + h < n && s[i + h] == s[j + h]) {
                h++;
            }
            lcp[rank[i]] = h;
            if (h > 0) h--;
        }
    }
    return lcp;
}

// 서로 다른 부분 문자열 개수 구하기
int count_unique_substrings(const string& s) {
    int n = s.size();

    // 접미사 배열과 LCP 배열 생성
    vector<int> suffix_array = build_suffix_array(s);
    vector<int> lcp = build_lcp_array(s, suffix_array);

    int total_substrings = n * (n + 1) / 2;  // 모든 부분 문자열의 개수
    int lcp_sum = 0;  // LCP들의 합

    for (int i = 1; i < n; i++) {
        lcp_sum += lcp[i];  // LCP 값의 합
    }

    return total_substrings - lcp_sum;  // 전체 부분 문자열에서 중복된 부분을 빼기
}

int main() {
    string s;
    cin >> s;

    int result = count_unique_substrings(s);
    cout << result << endl;

    return 0;
}

 

확실히 공간,시간 복잡도가 확연히 줄어든 모습을 볼 수있다. 이론은 이해할 수 있었지만 이를 구현하기는 어렵지만 최적화가 필요할 때는 이 방법을 활용하면 좋을 것같다.

https://www.acmicpc.net/problem/1269

 

각 집합 A,B에 대한 입력을 받고 A-B와 B-A의 합집합을 구하면 된다. 나는 set 자료형을 사용하여 두 집합을 입력받은 뒤 각 집합을 순회하면서 find함수를 통해 해당원소가 다른 집합의 원소가 아니라면 정답 set자료형에 넣어주고 마지막으로 그 자료형의 크기를 출력해주었다. 

하지만 STL의 set_difference 알고리즘을 사용하는 것이 각 집합간의 차집합을 구하는데 효율적일 수 있다.

set_difference는 정렬된 범위에서 한 집합에서 다른 집합에 없는 요소를 추출한다.

 

set_difference 함수는 각 집합의 시작과 끝, 결과값을 저장할 변수를 매개변수로 사용한다.

 

set_difference 

template< class InputIt1, class InputIt2, class OutputIt >
OutputIt set_difference( InputIt1 first1, InputIt1 last1,
                         InputIt2 first2, InputIt2 last2,
                         OutputIt d_first );

 

정답코드1(순회)

#include "iostream"
#include "set"

using namespace std;

set<int> s1, s2, answer;
int main()
{
	int n, m;

	cin >> n >> m;

	//집합만들기
	for (int i = 0; i < n; i++)
	{
		int x;
		cin >> x;
		s1.insert(x);
	}

	for (int i = 0; i < m; i++)
	{
		int x;
		cin >> x;
		s2.insert(x);
	}

	int cnt = 0;

	for (auto it = s1.begin(); it != s1.end(); it++)
	{
		if (s2.find(*it) == s2.end()) cnt++;
	}

	for (auto it = s2.begin(); it != s2.end(); it++)
	{
		if (s1.find(*it) == s1.end()) cnt++;
	}

	cout << cnt << endl;
}

 

 

정답코드2(STL알고리즘 활용)

#include <iostream>
#include <set>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;

    set<int> s1, s2;  // 두 집합을 저장할 set

    // 집합 A 입력
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        s1.insert(x);
    }

    // 집합 B 입력
    for (int i = 0; i < m; i++) {
        int x;
        cin >> x;
        s2.insert(x);
    }

    vector<int> diff1, diff2;  // 차집합을 저장할 벡터

    // A - B 차집합
    set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), back_inserter(diff1));

    // B - A 차집합
    set_difference(s2.begin(), s2.end(), s1.begin(), s1.end(), back_inserter(diff2));

    // 대칭 차집합의 원소 개수
    cout << diff1.size() + diff2.size() << endl;

    return 0;
}

https://inf.run/z3vzQ

 

학습 페이지

 

www.inflearn.com

 

 


오늘은 충돌과 피킹과 같은 요소를 다루기 전에 기본기를 다뤄보자

 

컴퓨팅쉐이더

1.이론

컴퓨팅쉐이더에 대해 알아보자. 이 쉐이더를 통해 cpu가 아닌 gpu에 일을 어떻게 넘기는지 그 방식에 대해 알 수 있다.

우리가 지금까지 작업해준 메인 코드는 모두 CPU에서 돌아가는 코드이고 엔진쪽에서 DX에 의해 처리되고 있는 버퍼와 

같은 코드들이 GPU에 의해 처리된다. 결과물이 렌더링 파이프라인을 통해서 GPU 를 통해 렌더링된다고 보면된다. 

GPU에게 일을 시키기 위해 쉐이더파일(.fx)을 만들어줬다.

GPU는 연산에 특화되어있다. 병렬로 처리할 수 있는 단순 작업을 CPU를 대신하여 GPU에서 처리를 해주면 좋은데 

이렇게 일반적인 그래픽스 용도가 아닌 범용적인 용도로 GPU를 사용하는 것을 GPGPU라고 한다. 

DX에서도 결국에는 컴퓨팅쉐이더를 이용해서 일반적인 쉐이더가 아니라 GPU에 단순 연산을 시키는 방식을 이용할 수 있다. 이러한 방식을 텍스처를 조작해서 다른 파일을 만들거나 하나의 스트림을 압축하거나 애니메이션에서 순간적인 상태의 애니메이션 위치를 알 수 있는 처리에 사용할 수 있다.

현재는 공용정보가 있다면 락을 거는 방식을 사용해서 다수의 스레드를 적용시켜 멀티셋 환경에서 작업을 할 수 있지만 이 작업 GPU를 통해 이제 해주어야한다.

 

일단 이 컴퓨팅 쉐이더를 사용하면서 배워보자. 핵심은 GPU에 어떤식으로 일을 분배할 것인가 이다. 

먼저 GPU에 데이터를 넘겼다가 다시 받으려면 리소스가 필요하다.

여러 리소스의 종류 중에 지금은 Raw Buffer(Byte Address Buffer)를 사용해보자. 이 Raw Buffer는 주소를 이용해서 

우리가 직접 주소값을 연산해서 접근하는형태로 바이트형 포인터를 다루는 느낌이라고 생각하면 된다. 이것도 리소스이기 때문에 리소스를 만들어주고 이를 묘사하는 view를 만들어주어야 한다. 실질적으로 GPU와 통신할 때는 이 View의 정보를 건네주고 이를 바탕으로 작업을 하고 받아 올때도 View를 통해 컴퓨팅 쉐이더에 건내준 다음 임시 버퍼를 통해 결과값을 취합한다.

 

2.코드

우선 RawBuffer 클래스를 만들어주자. 우리가 이 클래스로 입력을 받아주고 컴퓨팅 쉐이더에 연산을 요청한 다음 이 결과값을 받아주면 된다. 

이를 위해 입력값을 받아주는 변수는 어떤값이 들어올지 모르기 때문에 보이드형 포인터로 선언해주자. 이는 cpu 메모리에 있는 정보이다. 예를 들어 벡터가 있다면 벡터의 첫번째 데이터의 주소를 주고 이를 통해 크기정보를 받아온다.

그리고 입출력의 크기값을 저장할 변수를 만들어주고 입력값은 생성자에서 받아주자. 이 입력값을 바탕으로 지정한 크기의 버퍼를 만들고 작업해주면 된다.

View를 통해 받아온 정보를 꺼내주기 위한 버퍼도 만들어주자.

 

결과값도 View에서 바로 가져올 수 는 없고 임시 버퍼를 통해 복사 한 다음 작업을 해서 가져와야한다. 

버퍼를 만든다음 cpu 메모리에서 데이터를 만들어주면 그것을 건네주는 함수와 컴퓨팅 쉐이더를 통한 호출이 완료된 다음에 임시 버퍼의 값을 가져오는 함수를 만들어주자. 이 과정은 맵과 언맵을 통해 해준다.

RawBuffer.h

#pragma once


class RawBuffer
{
public:
	RawBuffer(void* inputData, uint32 inputByte, uint32 outputByte);
	~RawBuffer();

public:
	void CreateBuffer();
	//입력값 넣어주기
	void CopyToInput(void* data);
	//결과값 가져오기
	void CopyFromOutput(void* data);

public:
	ComPtr<ID3D11ShaderResourceView> GetSRV() { return _srv; }
	ComPtr<ID3D11UnorderedAccessView> GetUAV() { return _uav; }

private:
	//버퍼만들기
	void CreateInput();
	//입력 SRV만들기
	void CreateSRV();
	//결과가져올버퍼 만들기
	void CreateOutput();
	//결과 가져올 UAV
	void CreateUAV();
	//결과값 취합 및 복사
	void CreateResult();

private:
	ComPtr<ID3D11Buffer> _input;
	ComPtr<ID3D11ShaderResourceView> _srv;
	ComPtr<ID3D11Buffer> _output;
	ComPtr<ID3D11UnorderedAccessView> _uav;
	//결과값을 복사해올 버퍼
	ComPtr<ID3D11Buffer> _result;
private:
	void* _inputData;
	uint32 _inputByte = 0;
	uint32 _outputByte = 0;
};

 

RawBuffer.cpp

#include "pch.h"
#include "RawBuffer.h"

RawBuffer::RawBuffer(void* inputData, uint32 inputByte, uint32 outputByte)
	:_inputData(inputData),_inputByte(inputByte),_outputByte(outputByte)
{
	CreateBuffer();
}

RawBuffer::~RawBuffer()
{
}

void RawBuffer::CreateBuffer()
{
	CreateInput();
	CreateSRV();
	CreateOutput();
	CreateUAV();
	CreateResult();
}

void RawBuffer::CopyToInput(void* data)
{
	D3D11_MAPPED_SUBRESOURCE subResource;
	//맵
	DC->Map(_input.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	{
		memcpy(subResource.pData, data, _inputByte);
	}
	//언맵
	DC->Unmap(_input.Get(), 0);
}

void RawBuffer::CopyFromOutput(void* data)
{
	// 출력 데이터 -> result에 복사
	DC->CopyResource(_result.Get(), _output.Get());

	D3D11_MAPPED_SUBRESOURCE subResource;
	DC->Map(_result.Get(), 0, D3D11_MAP_READ, 0, &subResource);
	{
		memcpy(data, subResource.pData, _outputByte);
	}
	DC->Unmap(_result.Get(), 0);
}

void RawBuffer::CreateInput()
{
	if (_inputByte == 0)
		return;

	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.ByteWidth = _inputByte;
	desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS; // RAW_BUFFER
	desc.Usage = D3D11_USAGE_DYNAMIC; // CPU-WRITE, GPU-READ
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

	D3D11_SUBRESOURCE_DATA subResource = { 0 };
	subResource.pSysMem = _inputData;

	if (_inputData != nullptr)
		CHECK(DEVICE->CreateBuffer(&desc, &subResource, _input.GetAddressOf()));
	else
		CHECK(DEVICE->CreateBuffer(&desc, nullptr, _input.GetAddressOf()));
}

void RawBuffer::CreateSRV()
{
	if (_inputByte == 0)
		return;

	D3D11_BUFFER_DESC desc;
	_input->GetDesc(&desc);

	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	ZeroMemory(&srvDesc, sizeof(srvDesc));
	//일종의 널포인터
	srvDesc.Format = DXGI_FORMAT_R32_TYPELESS; // 쉐이더에서 알아서 하세요
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX; // SRV_FLAG_RAW
	srvDesc.BufferEx.Flags = D3D11_BUFFEREX_SRV_FLAG_RAW;
	srvDesc.BufferEx.NumElements = desc.ByteWidth / 4; // 전체 데이터 개수 - 전체 / 하나=4

	CHECK(DEVICE->CreateShaderResourceView(_input.Get(), &srvDesc, _srv.GetAddressOf()));
}

void RawBuffer::CreateOutput()
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.ByteWidth = _outputByte;
	desc.BindFlags = D3D11_BIND_UNORDERED_ACCESS;
	desc.MiscFlags = D3D11_RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS;

	CHECK(DEVICE->CreateBuffer(&desc, NULL, _output.GetAddressOf()));
}

//인풋SRV와 대칭적
void RawBuffer::CreateUAV()
{
	D3D11_BUFFER_DESC desc;
	_output->GetDesc(&desc);

	D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
	ZeroMemory(&uavDesc, sizeof(uavDesc));
	uavDesc.Format = DXGI_FORMAT_R32_TYPELESS;
	uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
	uavDesc.Buffer.Flags = D3D11_BUFFER_UAV_FLAG_RAW;
	uavDesc.Buffer.NumElements = desc.ByteWidth / 4;

	CHECK(DEVICE->CreateUnorderedAccessView(_output.Get(), &uavDesc, _uav.GetAddressOf()));
}

void RawBuffer::CreateResult()
{
	D3D11_BUFFER_DESC desc;
	_output->GetDesc(&desc);

	desc.Usage = D3D11_USAGE_STAGING;
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
	desc.BindFlags = D3D11_USAGE_DEFAULT; // UAV가 연결되려면, USAGE는 DEFAULT여야 함.
	desc.MiscFlags = 0;

	CHECK(DEVICE->CreateBuffer(&desc, nullptr, _result.GetAddressOf()));
}

 

이렇게 만들어준 버퍼를 메인쪽에서 사용할 수 있도록 코드를 구현해보자. 일단은 Input 없이 Output만 받아주는 방식으로 만들어주자. 먼저 컴퓨트 쉐이더를 사용하여 연산을 해줄 shader 코드를 구현하자. 이때 output의 offset은 받아주는 구조체의 크기와 바이트 개수를 통해 계산한다. 이를 통해 데이터가 연속해서 배열로 있다면 그 데이터를 캐릭터 / 바이트 포인터로 연산해서 주소를 찾아가야 한다면 몇번째 데이터인지 각 데이터의 크기를 통해  offset을 정해줘서 넘어갈 수 있다. 결국 이 쉐이더 코드는 CPU에서 하는 작업으로 치자면 구조체를 만들고 이 구조체변수에 값을 할당해주는 작업이라고 보면 된다.

RawBufferDemo.fx

#pragma once
#include "IExecute.h"

class RawBufferDemo : public IExecute
{
	struct Output
	{
		uint32 groupID[3];
		uint32 groupThreadID[3];
		uint32 dispatchThreadID[3];
		uint32 groupIndex;
	};

public:
	void Init() override;
	void Update() override;
	void Render() override;

private:
	shared_ptr<Shader> _shader;

};

 

이제 메인함수에서 작동하는 지 확인해보기 위해 Output으로 받을 구조체와 쓰레드 개수와 결과값을 받기위해 UAV 설정을 해주고 이를 확인하기 위해 엑셀파일로 파일 출력을 해주도록 하자. 

RawBufferDemo.h

#pragma once
#include "IExecute.h"

class RawBufferDemo : public IExecute
{
	struct Output
	{
		uint32 groupID[3];
		uint32 groupThreadID[3];
		uint32 dispatchThreadID[3];
		uint32 groupIndex;
	};

public:
	void Init() override;
	void Update() override;
	void Render() override;

private:
	shared_ptr<Shader> _shader;

};

RawBufferDemo.cpp

#include "pch.h"
#include "RawBufferDemo.h"
#include "RawBuffer.h"

void RawBufferDemo::Init()
{
	_shader = make_shared<Shader>(L"24. RawBufferDemo.fx");

	//하나의 쓰레드 그룹 내에서 운영할 쓰레드 개수
	uint32 count = 10 * 8 * 3;

	shared_ptr<RawBuffer> rawBuffer = make_shared<RawBuffer>(nullptr, 0, sizeof(Output) * count);

	//결과 묘사하는뷰 - 결과 값을 받을 UAV
	_shader->GetUAV("Output")->SetUnorderedAccessView(rawBuffer->GetUAV().Get());

	// x, y, z => 쓰레드 그룹
	_shader->Dispatch(0, 0, 1, 1, 1);

	vector<Output> outputs(count);
	rawBuffer->CopyFromOutput(outputs.data());

	FILE* file;
	::fopen_s(&file, "../RawBuffer.csv", "w");

	::fprintf
	(
		file,
		"GroupID(X),GroupID(Y),GroupID(Z),GroupThreadID(X),GroupThreadID(Y),GroupThreadID(Z),DispatchThreadID(X),DispatchThreadID(Y),DispatchThreadID(Z),GroupIndex\n"
	);

	for (uint32 i = 0; i < count; i++)
	{
		const Output& temp = outputs[i];

		::fprintf
		(
			file,
			"%d,%d,%d,	%d,%d,%d,	%d,%d,%d,	%d\n",
			temp.groupID[0], temp.groupID[1], temp.groupID[2],
			temp.groupThreadID[0], temp.groupThreadID[1], temp.groupThreadID[2],
			temp.dispatchThreadID[0], temp.dispatchThreadID[1], temp.dispatchThreadID[2],
			temp.groupIndex
		);
	}

	::fclose(file);
}

void RawBufferDemo::Update()
{

}

void RawBufferDemo::Render()
{

}

 

이렇게해서 실행해주면 엑셀파일이 하나 생긴다. 

지금은 규칙과 의미를 찾기가 힘들지만 앞으로 계속 활용해보면서 의미를 찾아나가야한다. 

컴퓨트 쉐이더를 실행해서 쓰레드를 사용해서 일을 시켜야한다. CPU에서는 Thread를 직접 관리해서 만든 다음에 

공용데이터를 이용하거나 일정 부분의 처리를 맡겼는데

GPU에서는 Thread가 많기 때문에 각각 지정해주는 것 보다

는 어떤 규칙성을 가지고 하는 것이 합리적이다. 지금은 보면 고유하게 넘버링이 있다는 것을 알면 된다. 우리가 지정해준

쓰레드 개후에 따라서 일관적인 규칙으로 넘버링이 붙는다.

이 중에 어떤 정보를 가지고 어떤 조작을 하는지에 따라 가공한 정보를 위처럼 몇번째  칸에 넣어주는지를 정해줄 수 있는 것이다. 

 

3. System Value 해석

이제 이 값이 어떤의미가 있는지 알아보자. 비유로 표현해보자면 칸 하나가 병사(쓰레드)라고 보면 된다. 

우리는 이런 병사가 240개를 지정해준 것이고 이 병사를 다 모아서 하나의 그룹이 되는 것이고 그 그룹이 몇개 있는지 

Dispatch함수를 통해 3차원으로 만들어주는 것이다.  

https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/sv-groupindex

 

이제 이SV값이 무엇을 의미하는지 알아보자. 

Group ThreadID는 하나의 그룹내에서 나의 쓰레드 ID는 몇번이지를 나타낸다. 이 값은 그룹내에서는 고유하다.

GroupID는 몇번째 그룹인지를 나타낸다.

Dispatch ThreadID는 자신이 몇번째인지 그룹자체의 번호와 해당하는 그룹에서 몇번인지를 구해주는 것으로 이 값은 모든 스레드마다 고유하게 있을 것이다.

GroupIndex는 Group ThreadID의 값을 활용해서 앞자리에 둔다. 7*1 + 5*10 + 0*10*8 끝에 10*8은  10,8,3에서 나온 것이다. 2차원배열을 1차원으로 만들 때 하나는 그냥 더해주고 하나는 사이즈에 곱해서 더해주는 식의 방식과 같이 인덱스를 표현해준 것이다. 결국에는 하나의 그룹내에서 몇번째 쓰레드인지 나타낸다. 

 

이것이 중요한 이유는 어떻게 데이터를 분할해서 시킬지에서 컴퓨트 쉐이더를 이용하는 경우도 있고 행렬을 이용해서 그 행렬을 여러 개의 쓰레드들이 담당하게 만들어 줄 수 있는 등의 다양한 형태로 분배를 해줄텐데 어떤 애가 어떤애를 참고할지는 이 SV값을 따라서 분할해서 일감을 주면 된다.

 

 

실습2

 

지금은 Output만 추출해주고 있지만 원래는 Input을 받아서 가공해서 다시 뱉어주는 것이 일반적이다. 

랜덤 값을 하나 정해준 다음 인풋에 넣어서 건네주고 이를 통해 쉐이더 파일을 거친 다음에 읽어오는 Read와 Write를 같이 한번 해보자. 

결국 굉장히 많은 데이터를 건네줬을 때 특정 쓰레드가 담당하는 그 공간에 있는 인풋을 가져와서 복사한 뒤 반환하는 것을 보고 싶은 것이다.

일단 입력 구조체를 만들고 값을 정해주자.

GroupDemo.h

#pragma once
#include "IExecute.h"

class GroupDemo : public IExecute
{
	struct Input
	{
		float value;
	};

	struct Output
	{
		uint32 groupID[3];
		uint32 groupThreadID[3];
		uint32 dispatchThreadID[3];
		uint32 groupIndex;
		float value;
	};

public:
	void Init() override;
	void Update() override;
	void Render() override;

private:
	shared_ptr<Shader> _shader;

};

GroupDemo.cpp

#include "pch.h"
#include "GroupDemo.h"
#include "RawBuffer.h"

void GroupDemo::Init()
{
	_shader = make_shared<Shader>(L"25. GroupDemo.fx");

	//하나의 쓰레드 그룹 내에서 운영할 쓰레드 개수
	uint32 threadCount = 10 * 8 * 3;
	uint32 groupCount = 2 * 1 * 1;
	//최종 데이터개수 - 그룹 수 * 그룹내 쓰레드의 수 
	uint32 count = groupCount * threadCount;

	vector<Input> inputs(count);
	for (int32 i = 0; i < count; i++)
		inputs[i].value = rand() % 10000;

	shared_ptr<RawBuffer> rawBuffer = make_shared<RawBuffer>(inputs.data(), sizeof(Input) * count, sizeof(Output) * count);

	_shader->GetSRV("Input")->SetResource(rawBuffer->GetSRV().Get());
	//결과 묘사하는뷰 - 결과 값을 받을 UAV,
	_shader->GetUAV("Output")->SetUnorderedAccessView(rawBuffer->GetUAV().Get());

	// x, y, z => 쓰레드 그룹
	_shader->Dispatch(0, 0, 2, 1, 1);

	vector<Output> outputs(count);
	rawBuffer->CopyFromOutput(outputs.data());

	FILE* file;
	::fopen_s(&file, "../RawBuffer.csv", "w");

	::fprintf
	(
		file,
		"GroupID(X),GroupID(Y),GroupID(Z),GroupThreadID(X),GroupThreadID(Y),GroupThreadID(Z),DispatchThreadID(X),DispatchThreadID(Y),DispatchThreadID(Z),GroupIndex,Value\n"
	);

	for (uint32 i = 0; i < count; i++)
	{
		const Output& temp = outputs[i];

		::fprintf
		(
			file,
			"%d,%d,%d,	%d,%d,%d,	%d,%d,%d,	%d,%f\n",
			temp.groupID[0], temp.groupID[1], temp.groupID[2],
			temp.groupThreadID[0], temp.groupThreadID[1], temp.groupThreadID[2],
			temp.dispatchThreadID[0], temp.dispatchThreadID[1], temp.dispatchThreadID[2],
			temp.groupIndex,temp.value
		);
	}

	::fclose(file);
}

void GroupDemo::Update()
{

}

void GroupDemo::Render()
{

}

 

그리고 Output만 있던 쉐이더에서 Input을 받을 수 있도록 수정해주자. ByteAddressBuffer로 입력을 정의해주고

그룹을 2개로 늘려줬기 때문에 이에 맞게 1차원에서 봤을 때의 각 쓰레드의 유니크한 인덱스를 계산해주고

이를 데이터 자료형크기에 따라 곱해준다음 그 주소값에서 입력 값을 가져와서 반환해주자.

GroupDemo.fx

ByteAddressBuffer Input;    // SRV
//Read + Write
RWByteAddressBuffer Output; // -> UAV

struct ComputeInput
{
    //SV: system Value 지정되어 있는 시스템 벨류
    uint3 groupID : SV_GroupID;
    uint3 groupThreadID : SV_GroupThreadID;
    uint3 dispatchThreadID : SV_DispatchThreadID;
    uint groupIndex : SV_GroupIndex;
};

//thread 개수 a*b*c 총합
[numthreads(10,8,3)]
void CS(ComputeInput input)
{
    //2차원 배열에서 유니크한 인덱스 계산 몇번째인지 2차원 -> 1차원
    uint index = input.groupID.x * (10 * 8 * 3) + input.groupIndex;
    //10(3+3+3+1+1) : 구조체 크기, 4: 바이트
    uint outAddress = index * 11 * 4;
    
    //데이터 : 4바이트 * 몇번째인지
    uint inAddress = index * 4;
    float value = (float)Input.Load(inAddress);
    
    //구조체에서 각 변수가 떨어진 정도
    Output.Store3(outAddress + 0, input.groupID);
    Output.Store3(outAddress + 12, input.groupThreadID);
    Output.Store3(outAddress + 24, input.dispatchThreadID);
    Output.Store3(outAddress + 36, input.groupIndex);
    Output.Store3(outAddress + 40, (uint)value);
}


technique11 T0
{
    Pass P0
    {
        SetVertexShader(NULL);
        SetPixelShader(NULL);
        //컴퓨트 쉐이더 세팅
        SetComputeShader(CompileShader(cs_5_0, CS()));
    }
};

 

그렇게 해주면 Value값이 더해진 엑셀파일이 생성된다.

 

이 엑셀파일을 내리다보면 240번째가 넘어서면 x값이 1이 증가하는 것을 볼 수 있다. 

쓰레드를 지정해줄 때 10,8,3 처럼 3차원으로 해주는 이유는 x y z를 통한 넘버링으로 이 값을 조정하면서

우리가 원하는 타입에 해당하는 정보를 빠르게 추출할 수 있다. 

지금처럼 ByteAddressBuffer를 통해 offset 주소를 계산해주면 정해지지 않은 임의의 형태의 데이터를 넣어줄 수 있지만 

다음에 배울 Structured Buffer를 이용해서 정해진 구조체를 통한 배열을 만들어서 관리하는 버퍼를 통해 더 쉽게 이 작업을 해줄 수 있다.

https://www.acmicpc.net/problem/1764

n개의 단어에서 나온 값과 m개의 단어에서 나온 값이 같은게 몇개인지 출력하고 해당하는 단어를 사전 순으로 출력해주면 되는 문제이다.

나는 map자료형을 통해 중복되는 단어를 vector에 저장하고 sort를 통해 사전순으로 정렬한 다음 해당 vector의 크기와 

원소를 순서대로 출력해주었다.

 

정답코드

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

using namespace std;

unordered_map<string, bool>hear;
vector<string> both;
int main()
{
	int n, m, cnt = 0;

	cin >> n >> m;

	for (int i = 0; i < n; i++)
	{
		string person;
		cin >> person;
		hear[person] = true;
	}

	for (int i = 0; i < m; i++)
	{
		string person;
		cin >> person;
		if (hear[person])
		{
			both.push_back(person);
		}
	}

	// 사전 순으로 정렬
	sort(both.begin(), both.end());

	cout << both.size() << '\n';  
	for (const auto& name : both) {
		cout << name << '\n'; 
	}

	return 0;
}

https://inf.run/9qb8B

 

학습 페이지

 

www.inflearn.com

 


오늘은  그래픽에서 많이 나오는 내용 중인 하나인 Quaternion에 대해 알아보자.

우선 복소수에 대해 알아야하는데 복소수는 제곱했을 때 -1이 나오는 수이다.

 

위의 그림에서 i가 허수부를 나타내는 수로 복소수이다. z에 작대기를 봍인것은 켤복소수로 허수부를 반대편 부호로 나타내는 것이다.

이 복소수는 극좌표에서 사용되곤한다. 곱해줬을 때 회전이 일어난다. 이 회전이 2D에서는 적용이 되었는데 3D에서는

적용되지 않았지만  4원소 사용하는 방법을 발견했고 이런 방법을 사용하는 것이  Quaternion이다. 

 

Quaternion을 이해 해보자

처음에 저런 식이 나온다. 저 식은 나눗셈이 아니라 a에서 b까지 변화하는 것이다. 이때 회전뿐만 아니라 크기도 변하고 

있다.  

3D에서 Quaternion을 사용하는 이유는 짐벌락 현상때문이다. x y z 축으로 한번씩 회전에서 모든 회전 값을  얻을 수 있을 줄 알았지만 축이 겹치면서 한축이 사라지면서 먹통이 되는 현상이 발생하는 것을 방지하기 위해서이다.

https://en.wikipedia.org/wiki/Gimbal_lock

Quaternion는 SRT에서 SR이 동시에 들어가 있는 것으로 생각하면 된다.

 



1,인스턴싱 통합

이제 만들어둔 인스턴싱 코드를 하나로 통합해보자 

쉐이더 코드를 합쳐주자. 인스턴싱 부분을 Render.fx로 만들어주자.

매쉬 인스턴싱 부분에서 각 물체의 월드좌표를  통해 정점의 정보를 입력받고 출력으로  내보내주는 VS부분을 가져오자

모델 인스턴싱 부분에서 뼈의 정보를 받고  루트로 향하는 뼈를 각 정점에 적용시켜주는 VS부분을 가져오자

애니메이션 부분에서는 각 프레임별로 글로벌에서 루트, 루트에서 다시 다른자세로 가는 행렬을 구해줬던 부분을 가져오자

Render.fx

#ifndef _RENDER_FX_
#define _RENDER_FX_

#include "00. Global.fx"

#define MAX_MODEL_TRANSFORMS 250
#define MAX_MODEL_KEYFRAMES 500
#define MAX_MODEL_INSTANCE 500

// ************** MeshRender ****************

struct VertexMesh
{
    float4 position : POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
	// INSTANCING;
    uint instanceID : SV_INSTANCEID;
    matrix world : INST;
};

MeshOutput VS_Mesh(VertexMesh input)
{
    MeshOutput output;

    output.position = mul(input.position, input.world); // W
    output.worldPosition = output.position;
    output.position = mul(output.position, VP);
    output.uv = input.uv;
    output.normal = input.normal;

    return output;
}

// ************** ModelRender ****************

struct VertexModel
{
    float4 position : POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float4 blendIndices : BLEND_INDICES;
    float4 blendWeights : BLEND_WEIGHTS;
	// INSTANCING;
    uint instanceID : SV_INSTANCEID;
    matrix world : INST;
};

cbuffer BoneBuffer
{
    matrix BoneTransforms[MAX_MODEL_TRANSFORMS];
};

uint BoneIndex;

MeshOutput VS_Model(VertexModel input)
{
    MeshOutput output;

    output.position = mul(input.position, BoneTransforms[BoneIndex]); // Model Global
    output.position = mul(output.position, input.world); // W
    output.worldPosition = output.position;
    output.position = mul(output.position, VP);
    output.uv = input.uv;
    output.normal = input.normal;

    return output;
}

// ************** AnimRender ****************

struct KeyframeDesc
{
    int animIndex;
    uint currFrame;
    uint nextFrame;
    float ratio;
    float sumTime;
    float speed;
    float2 padding;
};

struct TweenFrameDesc
{
    float tweenDuration;
    float tweenRatio;
    float tweenSumTime;
    float padding;
    KeyframeDesc curr;
    KeyframeDesc next;
};

cbuffer TweenBuffer
{
    TweenFrameDesc TweenFrames[MAX_MODEL_INSTANCE];
};

Texture2DArray TransformMap;

matrix GetAnimationMatrix(VertexModel input)
{
    float indices[4] = { input.blendIndices.x, input.blendIndices.y, input.blendIndices.z, input.blendIndices.w };
    float weights[4] = { input.blendWeights.x, input.blendWeights.y, input.blendWeights.z, input.blendWeights.w };

    int animIndex[2];
    int currFrame[2];
    int nextFrame[2];
    float ratio[2];

    animIndex[0] = TweenFrames[input.instanceID].curr.animIndex;
    currFrame[0] = TweenFrames[input.instanceID].curr.currFrame;
    nextFrame[0] = TweenFrames[input.instanceID].curr.nextFrame;
    ratio[0] = TweenFrames[input.instanceID].curr.ratio;

    animIndex[1] = TweenFrames[input.instanceID].next.animIndex;
    currFrame[1] = TweenFrames[input.instanceID].next.currFrame;
    nextFrame[1] = TweenFrames[input.instanceID].next.nextFrame;
    ratio[1] = TweenFrames[input.instanceID].next.ratio;

    float4 c0, c1, c2, c3;
    float4 n0, n1, n2, n3;
    matrix curr = 0;
    matrix next = 0;
    matrix transform = 0;

    for (int i = 0; i < 4; i++)
    {
        c0 = TransformMap.Load(int4(indices[i] * 4 + 0, currFrame[0], animIndex[0], 0));
        c1 = TransformMap.Load(int4(indices[i] * 4 + 1, currFrame[0], animIndex[0], 0));
        c2 = TransformMap.Load(int4(indices[i] * 4 + 2, currFrame[0], animIndex[0], 0));
        c3 = TransformMap.Load(int4(indices[i] * 4 + 3, currFrame[0], animIndex[0], 0));
        curr = matrix(c0, c1, c2, c3);

        n0 = TransformMap.Load(int4(indices[i] * 4 + 0, nextFrame[0], animIndex[0], 0));
        n1 = TransformMap.Load(int4(indices[i] * 4 + 1, nextFrame[0], animIndex[0], 0));
        n2 = TransformMap.Load(int4(indices[i] * 4 + 2, nextFrame[0], animIndex[0], 0));
        n3 = TransformMap.Load(int4(indices[i] * 4 + 3, nextFrame[0], animIndex[0], 0));
        next = matrix(n0, n1, n2, n3);

        matrix result = lerp(curr, next, ratio[0]);

		// 다음 애니메이션
        if (animIndex[1] >= 0)
        {
            c0 = TransformMap.Load(int4(indices[i] * 4 + 0, currFrame[1], animIndex[1], 0));
            c1 = TransformMap.Load(int4(indices[i] * 4 + 1, currFrame[1], animIndex[1], 0));
            c2 = TransformMap.Load(int4(indices[i] * 4 + 2, currFrame[1], animIndex[1], 0));
            c3 = TransformMap.Load(int4(indices[i] * 4 + 3, currFrame[1], animIndex[1], 0));
            curr = matrix(c0, c1, c2, c3);

            n0 = TransformMap.Load(int4(indices[i] * 4 + 0, nextFrame[1], animIndex[1], 0));
            n1 = TransformMap.Load(int4(indices[i] * 4 + 1, nextFrame[1], animIndex[1], 0));
            n2 = TransformMap.Load(int4(indices[i] * 4 + 2, nextFrame[1], animIndex[1], 0));
            n3 = TransformMap.Load(int4(indices[i] * 4 + 3, nextFrame[1], animIndex[1], 0));
            next = matrix(n0, n1, n2, n3);

            matrix nextResult = lerp(curr, next, ratio[1]);
            result = lerp(result, nextResult, TweenFrames[input.instanceID].tweenRatio);
        }

        transform += mul(weights[i], result);
    }

    return transform;
}

MeshOutput VS_Animation(VertexModel input)
{
    MeshOutput output;

	//output.position = mul(input.position, BoneTransforms[BoneIndex]); // Model Global

    matrix m = GetAnimationMatrix(input);

    output.position = mul(input.position, m);
    output.position = mul(output.position, input.world); // W
    output.worldPosition = output.position;
    output.position = mul(output.position, VP);
    output.uv = input.uv;
    output.normal = mul(input.normal, (float3x3) input.world);
    output.tangent = mul(input.tangent, (float3x3) input.world);

    return output;
}

#endif

 

PS부분은 그대로 두고 pass를 지정해주는거에 따라 매쉬,모델,애니메이션 중에 하나를 선택하도록 pass를 만들어주자.

RenderDemo.fx

#include "00. Global.fx"
#include "00. Light.fx"
#include "00. Render.fx"



float4 PS(MeshOutput input) : SV_TARGET
{
	//float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);

    float4 color = DiffuseMap.Sample(LinearSampler, input.uv);

    return color;
}

technique11 T0
{
	PASS_VP(P0, VS_Mesh, PS)
	PASS_VP(P1, VS_Model, PS)
	PASS_VP(P2, VS_Animation, PS)
};

 

이렇게 해주면 우리가 pass만 지정해주면 모델, 매쉬, 애니메이션 어떤것을 적용할지 정해줄 수 있다. 이제 오브젝트를 생성하는데 기존에 했던 오브젝트 생성에서 반복문 부분을 가져와서 모델, 매쉬, 애니메이션을 하나의 씬에 등장시키자

RenderDemo.cpp

#include "pch.h"
#include "RenderDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"

void RenderDemo::Init()
{
	RESOURCES->Init();
	_shader = make_shared<Shader>(L"23. RenderDemo.fx");

	// Camera
	_camera = make_shared<GameObject>();
	_camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
	_camera->AddComponent(make_shared<Camera>());
	_camera->AddComponent(make_shared<CameraScript>());

	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");

	for (int32 i = 0; i < 500; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->GetOrAddTransform()->SetScale(Vec3(0.01f));
		obj->AddComponent(make_shared<ModelAnimator>(_shader));
		{
			obj->GetModelAnimator()->SetModel(m1);
			obj->GetModelAnimator()->SetPass(2);
		}
		_objs.push_back(obj);
	}

	// Model
	shared_ptr<class Model> m2 = make_shared<Model>();
	m2->ReadModel(L"Tower/Tower");
	m2->ReadMaterial(L"Tower/Tower");

	for (int32 i = 0; i < 100; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->GetOrAddTransform()->SetScale(Vec3(0.01f));

		obj->AddComponent(make_shared<ModelRenderer>(_shader));
		{
			obj->GetModelRenderer()->SetModel(m2);
			obj->GetModelRenderer()->SetPass(1);
		}

		_objs.push_back(obj);
	}

	// Mesh
	// Material
	{
		shared_ptr<Material> material = make_shared<Material>();
		material->SetShader(_shader);
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		material->SetDiffuseMap(texture);
		MaterialDesc& desc = material->GetMaterialDesc();
		desc.ambient = Vec4(1.f);
		desc.diffuse = Vec4(1.f);
		desc.specular = Vec4(1.f);
		RESOURCES->Add(L"Veigar", material);
	}

	for (int32 i = 0; i < 100; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}

		_objs.push_back(obj);
	}


	RENDER->Init(_shader);
}

void RenderDemo::Update()
{
	_camera->Update();
	RENDER->Update();

	{
		LightDesc lightDesc;
		lightDesc.ambient = Vec4(0.4f);
		lightDesc.diffuse = Vec4(1.f);
		lightDesc.specular = Vec4(0.1f);
		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
		RENDER->PushLightData(lightDesc);
	}

	// INSTANCING
	INSTANCING->Render(_objs);
}

void RenderDemo::Render()
{

}

 

이렇게 해주면 기존에 했던 오브젝트들이 모두 등장하면서 프레임도 잘 유지되는것을 볼 수 있다.

 

 

2.Scene 구조 통합

지금 Update에는 카메라, 렌더, 빛을 각각 업데이트문을 넣어줬는데 이를 하나로 모아서 unity에서 Scene 단위로 묶어서 관리해주자. 이때 조명은 하나의 컴포넌트로 만들어줘서 관리해주자.

지금은 만들어준 Directional Light 코드를 그대로 사용하겠지만 다른 형태의 Light를 만들어주려고 하면 빛의 범위만 수학적으로 조정해주면 된다.

Light.h

#pragma once


#include "Component.h"
class Light : public Component
{
public:
	Light();
	virtual ~Light();

	virtual void Update();

public:
	LightDesc& GetLightDesc() { return _desc; }

	void SetLightDesc(LightDesc& desc) { _desc = desc; }
	void SetAmbient(const Color& color) { _desc.ambient = color; }
	void SetDiffuse(const Color& color) { _desc.diffuse = color; }
	void SetSpecular(const Color& color) { _desc.specular = color; }
	void SetEmissive(const Color& color) { _desc.emissive = color; }
	void SetLightDirection(Vec3 direction) { _desc.direction = direction; }

private:
	LightDesc _desc;
};

Light.cpp

#include "pch.h"
#include "Light.h"

Light::Light() : Component(ComponentType::Light)
{

}

Light::~Light()
{

}

void Light::Update()
{
	RENDER->PushLightData(_desc);
}

 

GameObject에서 Light Component를 사용할 수 있도록 헬퍼함수를 추가해주자.

GameObject.h

#pragma once
#include "Component.h"
class MonoBehaviour;
class Transform;
class Camera;
class MeshRenderer;
class ModelRenderer;
class ModelAnimator;
class Light;

class GameObject : public enable_shared_from_this<GameObject>
{
public:
	shared_ptr<Light> GetLight();
};

GameObject.cpp

#include "pch.h"
#include "GameObject.h"
#include "MonoBehaviour.h"
#include "Transform.h"
#include "Camera.h"
#include "MeshRenderer.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Light.h"

std::shared_ptr<Light> GameObject::GetLight()
{
	shared_ptr<Component> component = GetFixedComponent(ComponentType::Light);
	return static_pointer_cast<Light>(component);
}

 

그리고 씬이라는 구조를 만들어줄 때 우리가 관리해주는 Manager 클래스를 만들어주자. 이 Manager는 씬의 전환과 현재이 무엇인지 가져올 수 있는 기능을 가지고 있도록 하자. 이때 Scene도 클래스로 만들어주자.

SceneManager.h

#pragma once
#include "Scene.h"

class SceneManager
{
	DECLARE_SINGLE(SceneManager);

public:
	void Update();

	template<typename T>
	void ChangeScene(shared_ptr<T> scene)
	{
		_currentScene = scene;
		scene->Start();
	}

	shared_ptr<Scene> GetCurrentScene() { return _currentScene; }

private:
	shared_ptr<Scene> _currentScene = make_shared<Scene>();
};

SceneManager.cpp

#include "pch.h"
#include "SceneManager.h"

void SceneManager::Update()
{
	if (_currentScene == nullptr)
		return;

	_currentScene->Update();
	_currentScene->LateUpdate();
}

이 Scene의 Update에서는 카메라를 Update하는 부분은 다른 연산이나 기능 중에 바뀌게 되면 뷰 프로젝션 연산값이 달라지게 되는데 이는 부자연스러운 움직임이 연출 되거나 위치가 이상해질 수 있기 때문에 카메라는 모든 Update가 끝난 LateUpdate나 그 후에 해주는것이 좋다.

그리고 Update나 Start의 과정에서 오브젝의 Update Start가 작동할텐데 이때 이 함수가 끝나거나 추가될 때까지는 set의 값이 바뀌면 안되기 때문에 한번 임시적으로 캐싱을 해주고 이 캐싱한 값을 순회하도록 하자.  

Scene.h

#pragma once


class Scene
{
public:
	virtual void Start();
	virtual void Update();
	virtual void LateUpdate();
	
	//추가
	virtual void Add(shared_ptr<GameObject> object);
	//제거
	virtual void Remove(shared_ptr<GameObject> object);

private:
	//물체를 가지고있는 추가 삭제 편하지만 순회에는 안좋다 검색활용
	unordered_set<shared_ptr<GameObject>> _objects;
	//카메라
	unordered_set<shared_ptr<GameObject>> _cameras;
	//빛
	unordered_set<shared_ptr<GameObject>> _lights;
};

Scene.cpp

#include "pch.h"
#include "Scene.h"

void Scene::Start()
{
	unordered_set<shared_ptr<GameObject>> objects = _objects;

	for (shared_ptr<GameObject> object : objects)
	{
		object->Start();
	}
}

void Scene::Update()
{
	unordered_set<shared_ptr<GameObject>> objects = _objects;

	for (shared_ptr<GameObject> object : objects)
	{
		object->Update();
	}

	// INSTANCING
	vector<shared_ptr<GameObject>> temp;
	temp.insert(temp.end(), objects.begin(), objects.end());
	INSTANCING->Render(temp);
}

void Scene::LateUpdate()
{
	unordered_set<shared_ptr<GameObject>> objects = _objects;

	for (shared_ptr<GameObject> object : objects)
	{
		object->LateUpdate();
	}
}

void Scene::Add(shared_ptr<GameObject> object)
{
	_objects.insert(object);

	if (object->GetCamera() != nullptr)
	{
		_cameras.insert(object);
	}

	if (object->GetLight() != nullptr)
	{
		_lights.insert(object);
	}
}

void Scene::Remove(shared_ptr<GameObject> object)
{
	_objects.erase(object);

	_cameras.erase(object);

	_lights.erase(object);
}

 

이제 이 매니저가 실행되도록 우리가 만들어준 구조에 넣어야하는데 각 매니저가 Update되는 Game 코드에 이 코드를

넣어주도록 하자.

그리고 메인코드를 정리하면서 RenderManager에서 Update할때 쓰는 PushGlobalData가 결국에는 카메라의 뷰 프로젝션 행렬에 따라 달라지는 것이기 때문에 이 함수도 카메라의 Update에서 이루어지도록 수정해주자.

Game.cpp

void Game::Update()
{
	TIME->Update();
	INPUT->Update();
	ShowFps();

	GRAPHICS->RenderBegin();

	SCENE->Update();

	GUI->Update();
	_desc.app->Update();
	_desc.app->Render();
	GUI->Render();

	GRAPHICS->RenderEnd();
}

Camera.cpp

void Camera::Update()
{
	UpdateMatrix();

	RENDER->PushGlobalData(Camera::S_MatView, Camera::S_MatProjection);
}

 

이제 메인코드를 수정해주면 되는데 각각 Component를 붙여주고 그것을 씬에 추가해주면 된다.

SceneDemo.h

#pragma once
#include "IExecute.h"

class SceneDemo : public IExecute
{
public:
	void Init() override;
	void Update() override;
	void Render() override;

private:
	shared_ptr<Shader> _shader;
};

SceneDemo.cpp

#include "pch.h"
#include "SceneDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"
#include "Material.h"
#include "Model.h"
#include "ModelRenderer.h"
#include "ModelAnimator.h"
#include "Mesh.h"
#include "Transform.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "Light.h"


void SceneDemo::Init()
{
	RESOURCES->Init();
	_shader = make_shared<Shader>(L"23. RenderDemo.fx");

	// Camera
	{
		auto camera = make_shared<GameObject>();
		camera->AddComponent(make_shared<Camera>());
		camera->AddComponent(make_shared<CameraScript>());
		camera->GetOrAddTransform()->SetPosition(Vec3{ 0.f, 0.f, -5.f });
		CUR_SCENE->Add(camera);
	}

	//Light
	{
		auto light = make_shared<GameObject>();
		light->AddComponent(make_shared<Light>());
		LightDesc lightDesc;
		lightDesc.ambient = Vec4(0.4f);
		lightDesc.diffuse = Vec4(1.f);
		lightDesc.specular = Vec4(0.1f);
		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
		light->GetLight()->SetLightDesc(lightDesc);
		CUR_SCENE->Add(light);
	}
	
	//Animation
	shared_ptr<class Model> m1 = make_shared<Model>();
	m1->ReadModel(L"Kachujin/Kachujin");
	m1->ReadMaterial(L"Kachujin/Kachujin");
	m1->ReadAnimation(L"Kachujin/Idle");
	m1->ReadAnimation(L"Kachujin/Run");
	m1->ReadAnimation(L"Kachujin/Slash");

	for (int32 i = 0; i < 500; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->GetOrAddTransform()->SetScale(Vec3(0.01f));
		obj->AddComponent(make_shared<ModelAnimator>(_shader));
		{
			obj->GetModelAnimator()->SetModel(m1);
			obj->GetModelAnimator()->SetPass(2);
		}
		CUR_SCENE->Add(obj);
	}

	// Model
	shared_ptr<class Model> m2 = make_shared<Model>();
	m2->ReadModel(L"Tower/Tower");
	m2->ReadMaterial(L"Tower/Tower");

	for (int32 i = 0; i < 100; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->GetOrAddTransform()->SetScale(Vec3(0.01f));

		obj->AddComponent(make_shared<ModelRenderer>(_shader));
		{
			obj->GetModelRenderer()->SetModel(m2);
			obj->GetModelRenderer()->SetPass(1);
		}

		CUR_SCENE->Add(obj);
	}

	// Mesh
	// Material
	{
		shared_ptr<Material> material = make_shared<Material>();
		material->SetShader(_shader);
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		material->SetDiffuseMap(texture);
		MaterialDesc& desc = material->GetMaterialDesc();
		desc.ambient = Vec4(1.f);
		desc.diffuse = Vec4(1.f);
		desc.specular = Vec4(1.f);
		RESOURCES->Add(L"Veigar", material);
	}

	for (int32 i = 0; i < 100; i++)
	{
		auto obj = make_shared<GameObject>();
		obj->GetOrAddTransform()->SetLocalPosition(Vec3(rand() % 100, 0, rand() % 100));
		obj->AddComponent(make_shared<MeshRenderer>());
		{
			obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"Veigar"));
		}
		{
			auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
			obj->GetMeshRenderer()->SetMesh(mesh);
			obj->GetMeshRenderer()->SetPass(0);
		}

		CUR_SCENE->Add(obj);
	}


	RENDER->Init(_shader);
}

void SceneDemo::Update()
{
	
}

void SceneDemo::Render()
{

}

 

이렇게 해주면 정상적으로 작동한다.

https://www.acmicpc.net/problem/1620

 

 

문제가 굉장히 길다 앞부분보다는 입력과 출력 위주로 보고 풀면된다. 나는 입력을 받은 다음에 map과 vector자료형을 

사용하여 만약 문자가 온다면 string과 저장순서가 int로 기록된 map을 통해 출력해주고 만약 숫자가 온다면 벡터안에 저장된 순서에서 1을 빼준 인덱스의 문자를 출력해주는 방식으로 풀어보았다. 

처음에 시간초과가 났는데 이유는 입출력의 크기가 크기 때문이라고 생각해서 cin과 cout를 scanf와 printf 로 바꿔서 풀어주었더니 시간안에 풀 수 있었다.

 

정답코드

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

using namespace std;

vector<string> pocket;
unordered_map<string, int> pocket2;
int main()
{
	int n, m;

	char buffer[21];  // 포켓몬 이름을 받을 버퍼

	scanf("%d %d", &n, &m);

	for (int i = 0; i < n; i++)
	{
		string pocketmon;
		scanf("%s", buffer);
		pocket.push_back(buffer);
		pocket2[buffer] = i + 1;
	}

	for (int i = 0; i < m; i++)
	{
		scanf("%s", buffer);
		if (isdigit(buffer[0]))
		{
			printf("%s\n", pocket[stoi(buffer)-1].c_str());
		}
		else
		{
			printf("%d\n", pocket2[buffer]);
		}
	}

	return 0;
}

https://www.acmicpc.net/problem/7785

 

이 문제는 문자열 변수 2개를 받고 만약 enter이라면 set 자료형을 통해 넣어주고 leave이면 제거해주면 된다. 그 후에 역순으로 출력해주면된다.

 

정답코드

#include "iostream"
#include "set"
#include "string"

using namespace std;

set<string> s;
int main() {
    int n;
    cin >> n;

    for (int i = 0; i < n; i++) {
        string name, action;
        cin >> name >> action;

        if (action == "enter") {
            s.insert(name);  // 회사에 들어왔으면 set에 추가
        }
        else if (action == "leave") {
            s.erase(name);  // 나갔으면 set에서 제거
        }
    }

    // 역순으로 출력
    for (auto it = s.rbegin(); it != s.rend(); ++it) {
        cout << *it << '\n';
    }

    return 0;
}

+ Recent posts