https://www.youtube.com/watch?v=ljmOsZVrtok&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=22

오늘은 AI가 플레이어를 감지하고 쫓아 다닐 수 있도록 만들어보자 

 

일단 AI_Controller에 컴포넌트를 추가해서 플레이어를 감지할 수 있도록 AIPerception 컴포넌트를 추가해준다.

이 컴포넌트에서 감지환경설정을 시야구성으로 바꿔준다.

이때 센스는 기본을 따르도록 하고 추후과정에서 이 값을 조절하기로 하자.

AIPerception 컴포넌트

귀속감지는 블루프린트 상의 오류방지를 위해 모두 체크해주자

 

타킷 퍼셉션 업데이트 시 이벤트 구성

 

블랙보드에 키를 추가해서 만약 감지했을 때 Target Actor이면 따라가고 아니면 돌아다니는 과정을 수행하도록 한다.

 

이제 AI 블랙보드 트리에서 Chase Target Sequence를 만들어주고 새로운 테스크를 만들어주자 

 

블랙보드 키를 가져와서 액터에 넣어주고 이를 따라가게 하자 멈추는 거리는 100정도로 설정해주자 

 

이후 블랙보드트리에서 

왼쪽에 Chase를 붙여서 이게 먼저 실행될 수 있도록 하며 BlackBoard값에 따라 해당 테스크가 수행되도록 데코레이터를 추가해준다.

전체 트리 및 Chase쪽 데코레이터 설정
Patrol쪽 데코레이터 설정

 

하지만 아직 플레이어를 감지하지 못하고 있다. 이 부분은 플레이어 쪽에 감지 자극소스를 부착해주면된다.

 

그리고 프로젝트 루트폴더에서 Config/DefaultGame.ini 파일을 수정해준다.

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

 

프로그래머스

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

programmers.co.kr

 

 

조합을 통해 키를 조합한 후 이에 대한 유일성과 최소성을 만족시키는 값을 찾아야한다.

 

유일성 : 유일한 값 

최소성 : 여러 속성을 조합해 만든 키가 유일성을 가질 때, 한 속성을 제외해도 유일성이 유지되면 최소성을 만족하지 않는다.

 

어떻게 검사할까 찾아보다가 비트마스크 방법으로 조합과 유일성 최소성 검사를 하는게 직관적이라는 것을 알게 되었다. 

조합

각 열을 하나의 비트로 나타내면, 비트마스크의 각 비트는 열의 포함 여부를 나타낼 수 있다. 

예를 들어, 열이 3개인 경우:

  • 000: 아무 열도 포함하지 않음
  • 001: 첫 번째 열만 포함
  • 010: 두 번째 열만 포함
  • 011: 첫 번째와 두 번째 열 포함
  • 100: 세 번째 열만 포함
  • 101: 첫 번째와 세 번째 열 포함
  • 110: 두 번째와 세 번째 열 포함
  • 111: 모든 열 포함

이런식으로 표현할 수 있다. 

이 방법을 통해 조합을 생성하려면 2^열의 수로 순회하면 된다. 

조합

 // 모든 가능한 속성 조합을 생성
    for (int comb = 1; comb < (1 << columns); comb++) {
        // 유일성과 최소성 검사
        if (isUnique(relation, columns, comb) && isMinimal(comb, candidateKeys)) {
            // 유일성과 최소성을 만족하는 경우 후보키로 추가
            candidateKeys.push_back(comb);
        }
    }

 

유일성 검사

유일성 검사또한 비트마스크를 통해 열 포함여부를 검사해준다. 

// 유일성을 검사하는 함수
bool isUnique(vector<vector<string>>& relation, int columns, int comb) {
    set<string> uniqueCheck;
    for (int i = 0; i < relation.size(); i++) {
        string key = "";
        for (int j = 0; j < columns; j++) {
            // comb의 각 비트를 확인하여 해당 열을 포함하는지 검사
            if (comb & (1 << j)) {
                key += relation[i][j] + " ";
            }
        }
        // 유일성 검사: 이미 존재하는 키라면 false 반환
        if (uniqueCheck.find(key) != uniqueCheck.end()) {
            return false;
        }
        uniqueCheck.insert(key);
    }
    return true;
}

 

이때 직접적인 검사는  if (comb & (1 << j)) 부분에서 이루어진다. 

comb & (1 << j) 이 부분에서 어떻게 계산이 이루어지는지는 예시를 통해 알아보자

 

  • comb의 j번째 비트가 1인지 검사하려면, 1 << j를 사용하여 j번째 비트만 1인 숫자를 만든다.
  • comb & (1 << j)를 수행하면, comb의 j번째 비트가 1일 때만 결과가 1이 됩니다. 그렇지 않으면 결과가 0이 된다.
  • comb = 5 (0101)
  • 열의 개수 columns = 4
  • j = 0:
    • 1 << 0는 0001
    • comb & 0001은 0101 & 0001이므로 결과는 0001 (참)
  • j= 1:
    • 1 << 1은 0010
    • comb & 0010은 0101 & 0010이므로 결과는 0000 (거짓)

 

  • j = 2:
    • 1 << 2은 0100
    • comb & 0100은 0101 & 0100이므로 결과는 0100 (참)
  • j = 3:
    • 1 << 3은 1000
    • comb & 1000은 0101 & 1000이므로 결과는 0000 (거짓)

j = 0과 j = 2에서 조건이 참이므로, 첫 번째와 세 번째 열의 값이 key에 추가

 

최소성 검사 

기존 후보키로 만들어진 것이 현재 조합의 부분집합에 포함된다면 후보키가 아니다.

// 최소성을 검사하는 함수
bool isMinimal(int comb, vector<int>& candidateKeys) {
    // 기존의 후보키들에 대해 검사
    for (int key : candidateKeys) {
        // 기존의 후보키가 현재 조합에 포함되는지 검사
        if ((comb & key) == key) {
            // 기존 후보키가 현재 조합의 부분집합이면 최소성 만족하지 않음
            return false;
        }
    }

    // 모든 기존 후보키가 현재 조합의 부분집합이 아니면 최소성 만족
    return true;
}

 

전체코드 

 

#include <iostream>
#include <vector>
#include <string>
#include <set>

using namespace std;

// 유일성을 검사하는 함수
bool isUnique(vector<vector<string>>& relation, int columns, int comb) {
    set<string> uniqueCheck;

    // 각 튜플(행)에 대해 검사
    for (int i = 0; i < relation.size(); i++) {
        string key = "";

        // comb의 각 비트를 확인하여 해당 열을 포함하는지 검사
        for (int j = 0; j < columns; j++) {
            if (comb & (1 << j)) {
                // comb의 j번째 비트가 1이면 해당 열을 key에 추가
                key += relation[i][j] + " ";
            }
        }

        // 유일성 검사: 이미 존재하는 키라면 false 반환
        if (uniqueCheck.find(key) != uniqueCheck.end()) {
            return false;
        }

        // 새로운 키를 set에 추가
        uniqueCheck.insert(key);
    }

    // 모든 튜플에 대해 유일성을 만족하면 true 반환
    return true;
}

// 최소성을 검사하는 함수
bool isMinimal(int comb, vector<int>& candidateKeys) {
    // 기존의 후보키들에 대해 검사
    for (int key : candidateKeys) {
        // 기존의 후보키가 현재 조합에 포함되는지 검사
        if ((comb & key) == key) {
            // 기존 후보키가 현재 조합의 부분집합이면 최소성 만족하지 않음
            return false;
        }
    }

    // 모든 기존 후보키가 현재 조합의 부분집합이 아니면 최소성 만족
    return true;
}

int solution(vector<vector<string>> relation) {
    int rows = relation.size();     // 튜플(행)의 개수
    int columns = relation[0].size(); // 속성(열)의 개수
    vector<int> candidateKeys;     // 후보키를 저장할 벡터

    // 모든 가능한 속성 조합을 생성
    for (int comb = 1; comb < (1 << columns); comb++) {
        // 유일성과 최소성 검사
        if (isUnique(relation, columns, comb) && isMinimal(comb, candidateKeys)) {
            // 유일성과 최소성을 만족하는 경우 후보키로 추가
            candidateKeys.push_back(comb);
        }
    }

    // 후보키의 개수 반환
    return candidateKeys.size();
}

int main() {
    vector<vector<string>> relation = {
        {"100", "ryan", "music", "2"},
        {"200", "apeach", "math", "2"},
        {"300", "tube", "computer", "3"},
        {"400", "con", "computer", "4"},
        {"500", "muzi", "music", "3"},
        {"600", "apeach", "music", "2"}
    };

    cout << solution(relation) << endl;  // 출력: 2
    return 0;
}

 

 

 

 

인덱스버퍼

현재 -> 사각형- 삼각형 2개  만약 이런식이라면 그 많은 도형을 표현하기엔 메모리가 너무 많이 소모된다.

-> 인덱스 버퍼사용

정점들에 인덱스(넘버)를 부여하여 사용

 

이걸 코드로 만들어보자 일단 정점을 하나 추가해보자 그리고 정점 순서에 맞게 좌표를 수정해준다.

//13 -> 012
//02 -> 213
_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
_vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);

 

인덱스버퍼에 필요한 변수를 만들고 Geometry에 인덱스 버퍼 부분을 만들어주자

변수 

//인덱스버퍼 - 이거도 Geometry에 포함
vector<uint32> _indices;
ComPtr<ID3D11Buffer> _indexBuffer = nullptr;
//Index
{
	_indices = { 0,1,2,2,1,3 };
}

//IndexBuffer
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.Usage = D3D11_USAGE_IMMUTABLE;			//gpu만 read only
	desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
	desc.ByteWidth = (uint32)(sizeof(uint32) * _indices.size());

	D3D11_SUBRESOURCE_DATA data;
	ZeroMemory(&data, sizeof(data));
	data.pSysMem = _indices.data();			//첫번째 시작주소 cpu값이 복사된다.

	HRESULT hr= _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf());
	CHECK(hr);
}

 

인덱스 버퍼 세팅

IA 세팅 과정 중에 -> 랜더에 있는 세팅부분에 추가해줘야한다. 

기존

	//IA - 세팅부분
	_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
	_deviceContext->IASetInputLayout(_inputLayout.Get());
	_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기

 

수정

	//IA - 세팅부분
	_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
	_deviceContext->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT,0);
	_deviceContext->IASetInputLayout(_inputLayout.Get());
	_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기
    
    
    //인덱스를 통해 그려주기
    _deviceContext->DrawIndexed(_indices.size(), 0, 0);

 

이렇게하면 Index버퍼를 사용할 준비가 완료되었다. 

 

실행화면

 

정점버퍼와 인덱스버퍼의 차이점

-> 인덱스버퍼는 삼각형에 넘어가는 정점 개수를 줄여줘 메모리적으로 효율적으로 작업할 수 있다.

 

정점버퍼와 인덱스버퍼의 조합으로 삼각형이 어떻게 만들어지는지 판단할 수 있다.

 

이제 사각에 이미지를 띄워보자 

 

중요한 점 : uv좌표, 쉐이더코드 수정

 

uv좌표 - 이미지의 좌상단 0,0 우하단 1,1로 했을 때 퍼센티지로 가져오기 위함 

uv좌표

 

Stuct.h 수정 및 정점정보 수정

#pragma once
#include "Types.h"


struct Vertex
{
	Vec3 position;		//12바이트 0부터시작
	//Color color;		//12부터시작
	Vec2 uv;			
};

 

//정점정보
{
	_vertices.resize(4);

	//13 -> 012
	//02 -> 213
	_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
	_vertices[0].uv = Vec2(0.f, 1.f);
	//_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[1].position = Vec3(-0.5f, 0.5f, 0.f);
	_vertices[1].uv = Vec2(0.f, 0.f);
	//_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
	_vertices[2].uv = Vec2(1.f, 1.f);
	//_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[3].position = Vec3(0.5f, 0.5f, 0.f);
	_vertices[3].uv = Vec2(1.f, 0.f);
	//_vertices[3].color = Color(1.f, 0.f, 0.f, 1.f);
}

 

그리고 이에 맞게 InputLayout을 고쳐줘야하는데 이 때 쉐이더와 맞춰주어야하기 때문에 

많이 사용하는 TEXCOORD - 텍스처의 좌를 사용한다. 

InputLayout 및 쉐이더 수정

struct VS_INPUT
{
    float4 position : POSITION;
    //float4 color : COLOR;
    float2 uv : TEXCOORD;
};

struct VS_OUTPUT
{
    float4 position : SV_POSITION;      //시스템 VALUE  무조건 있어야한다.
    //float4 color : COLOR;
    float2 uv : TEXCOORD;
};
	//입력에 대한 정보 ~바이트부터 뛰면 칼러인지 
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
		{"TEXCOORD",0,DXGI_FORMAT_R32G32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
	};

 

이미지파일 소스를 gpu쪽에도 건네줘서 알고있도록 해야한다. 

레지스터에 등록해주기

//함수의 인자같은 존재, 오브젝트마다 달라질수있다.
Texture2D texture0 : register(t0);      //레지스터에 등록
SamplerState sampler0 : register(s0);

 

등록해준 레지스터를 통해 텍스처 색상을 가져오기 

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    float4 color = texture0.Sample(sampler0, input.uv);     //텍스처0번 uv좌표를 통해 색상 빼오기
    
    return color;
}

해당 이미지파일을 cpu에서 리소스로 가지고 gpu쪽에도 만들어준 다음 파이프라인에 연결

 

픽셀쉐이더부분에서 CreateSRV를 통해 할 수 있다.

SRV 쉐이더 리소스 뷰 만들어주기 

SRV변수 추가 -이미지를 어떻게 쓸것인가 

이것을 통해 이미지를 건네준다.  

//SRV - 이미지를 어떻게 쓸것인가
ComPtr<ID3D11ShaderResourceView> _shaderResourceView = nullptr;
void Game::CreateSRV()
{
	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	HRESULT hr = ::LoadFromWICFile(L"cat.png", WIC_FLAGS_NONE, &md, img);
	CHECK(hr);

	//쉐이더리소스뷰 만들기
	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	CHECK(hr);
}

 

이제 Init부분에 함수를 넣고 Render의 PS부분에서 이미지와 연결시켜주자 

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;


	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	//삼각형 그리기 파트
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();

	CreateSRV();
}
	//PS
	_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
	_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf());		//0번슬롯에 1개

 

실행화면 

 

uv 1일때 

uv 0.5일때

 

uv좌표가 1을 초과했을 때 어떻게 할지

-> 레스터라이저단계의 샘플스테이트를 통해 관리 

 

어떤게 범용적 어떤게 오브젝트에 종속적인지 생각해보기 

ex) GeoMetry - 매쉬 - 오브젝트 대상

쉐이더 - 범용적

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11]4. 삼각형 그리기  (1) 2024.07.01
[Directx11]3.장치초기화  (0) 2024.06.27
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (0) 2024.06.25
[Directx11]2.랜더링 파이프라인2  (0) 2024.06.21

https://www.youtube.com/watch?v=UeG9RAVE8sE&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=21

 

오늘은 적이 가지게 될 AI행동 기초를 만들어 볼 것이다.

 

일단 AI폴더를 만들어주고 안에

여러 npc의 기본 바탕이 될 Black Board와 Behavior Tree, AIController 블루프린트 크래스를 만들어준다. 

 AIController 클래스에서는 시작될 때 , Run Behavior Tree를 실행시켜 만들어둔 Behavior Tree가 작동하도록 한

다. 

 

전에 만들어둔 더미캐릭터를 AI폴더로 가지고 온다. 

그리고 디테일 창에 ai를 검색한 후 폰부분을 바꿔주면 된다.

Place or Spawned가 오류를 줄일 수 있다. 

 

이제 본격적인 흐름도를 만들어보자 이건 트리상에 만들 것인데 Selector와 Seqeunce를 통해 트리구조를 만들고 

새 테스크를 Task폴더에 만들어주자 이번 Task는 Patrol Random Point이다. 

이 테스크는 AI플레이가 시작될 때 호출되는 Receive Execute AI라는 이벤트를 오버라이드 해주는 것으로 동작하도록 만든다. 

 

radius 안의 원의 랜덤한 위치로 움직일 수 있도록 만들어본다. 

그리고 Tree안에 Task를 추가해준다. 

 

그리고 NavMeshBoundsVolume을 통해 AI가 갈 수 있는 길을 표시해준다. 

->이것을 통해 AI가 움직일 수 있다. 

P키를 누르면 AI가 갈 수 있는 모든 길을 보여준다. 

 

이제 랜덤한 위치로 AI가 이동한다.

 

움직이는 AI

 

이제 AI가 자연스럽게 움직이도록 만들어보자 

애니메이션 블루프린트를 만들고 이전에 만들어둔 플레이어의 애니메이션 블루프린터를 참고하자. 

-> 스테이트를 통해 관리되고 있다.

캐릭터 애니메이션 블루프린트의 Locomotion을 복사해서 DefaultSlot에서 작동하도록 만들어 주자

그다음 AI의 매쉬에서 애니메이션 클래스를 변경해준다. 

 

그리고 이벤트 그래프에 속도를 조절하는 부분을 플레이어 부분에서 가져와서 붙여주면 움직이는 모습을 볼 수 있다.

움직이게 하기

 

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

 

프로그래머스

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

programmers.co.kr

 

 

할인율을 정하고 이에 대한 모든 이모티콘을 dfs로 탐색해서 시뮬레이션을 돌리면 된다. 

 

중요한 부분은

할인율을 정하고 dfs 돌리는 부분

  for (int i = 0; i < 4; i++) {
        price[cnt].first = (100 - dis[i]) * emoticons[cnt] / 100;  // 할인된 가격
        price[cnt].second = dis[i];  // 할인율
        dfs(cnt + 1, n, users, emoticons);
    }

한 할인율을 이모티콘들에 다 적용했을 때의 이윤계산 

if (cnt == n) {  // 모든 이모티콘에 할인율을 대입했을 때 
        int plus = 0, sum = 0;
        for (int i = 0; i < users.size(); i++) {
            int tmp = 0;
            for (int j = 0; j < n; j++)
                if (users[i][0] <= price[j].second) // 원하는 할인율 이상이면 구매
                    tmp += price[j].first;  
            if (tmp >= users[i][1]) // 구매값이 정해진 가격 이상이면 이모티콘플러스 가입
                plus++;  
            else
                sum += tmp;  // 이윤++
        }
        if (plus > answer[0]) {  // 가입자수가 더 많으면
            answer[0] = plus;
            answer[1] = sum;
        } else if (plus == answer[0] && sum >= answer[1])
            answer[1] = sum;  // 가입자수가 같고 이윤이 크면
        return;
    }

 

전체코드

#include <string>
#include <vector>
using namespace std;

int dis[4] = {40, 30, 20, 10};  // 할인율
vector<pair<int, int>> price(7, {0, 0});  // 할인된 가격, 할인율
vector<int> answer(2, 0);

void dfs(int cnt, int n, vector<vector<int>> users, vector<int> emoticons) {
    if (cnt == n) {  // 모든 이모티콘에 할인율을 대입했을 때 
        int plus = 0, sum = 0;
        for (int i = 0; i < users.size(); i++) {
            int tmp = 0;
            for (int j = 0; j < n; j++)
                if (users[i][0] <= price[j].second) // 원하는 할인율 이상이면 구매
                    tmp += price[j].first;  
            if (tmp >= users[i][1]) // 구매값이 정해진 가격 이상이면 이모티콘플러스 가입
                plus++;  
            else
                sum += tmp;  // 이윤++
        }
        if (plus > answer[0]) {  // 가입자수가 더 많으면
            answer[0] = plus;
            answer[1] = sum;
        } else if (plus == answer[0] && sum >= answer[1])
            answer[1] = sum;  // 가입자수가 같고 이윤이 크면
        return;
    }
    for (int i = 0; i < 4; i++) {
        price[cnt].first = (100 - dis[i]) * emoticons[cnt] / 100;  // 할인된 가격
        price[cnt].second = dis[i];  // 할인율
        dfs(cnt + 1, n, users, emoticons);
    }
}

vector<int> solution(vector<vector<int>> users, vector<int> emoticons) {
    dfs(0, emoticons.size(), users, emoticons);
    return answer;
}

 

오늘은 삼각형을 그려볼 것이다. 

 

삼각형 하나를 그리더라도 전체 파이프라인은 한번 거쳐야한다. 

 

 

일단 기하학적인 도형을 만들어주는 함수를 만들고 정점을 나타내줄 구조체도 선언해준다.

 

Stuct.h

#pragma once
#include "Types.h"


struct Vertex
{
	Vec3 position;		//12바이트 0부터시작
	Color color;		//12부터시작
};

 

 

정점 선언해주기 

정점은 -1,1 사이의 범위에서 선언해준다. -> 화면 상의 좌표때문에 

정점 정보까지는 아직 CPU에서의 영역, 

RAM<->CPU VRAM<->GPU

->VRAM에도 같은 정보를 만들어줘야한다. 

-> _device->CreateBuffer(); 마지막인자로 정점버퍼를 받는다 -> 이를 통해 정점버퍼 초기화

 

D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Usage = D3D11_USAGE_IMMUTABLE;			//디폴트- GPU만 읽고 쓸수있음 
IMMUTABLE GPU만 읽을 수 있다. 
DYNAMIC- GPU : 읽기 CPU: 쓰기 
STAGING - CPU->GPU 데이터전송

 

메모리에 있는 정점 넘겨주기 전체코드 

이렇게 되면 이제 _vertices가 가지고있던 정점 정보를 gpu쪽으로 넘겨줘서 이후에는 gpu만 read only할 수 있도록 한다. 

//정점정보
{
	_vertices.resize(3);

	_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
	_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[1].position = Vec3(0.f, 0.5f, 0.f);
	_vertices[1].color = Color(1.f, 0.f, 0.f, 1.f);
	_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
	_vertices[2].color = Color(1.f, 0.f, 0.f, 1.f);
}

//정점버퍼
{
	D3D11_BUFFER_DESC desc;
	ZeroMemory(&desc, sizeof(desc));
	desc.Usage = D3D11_USAGE_IMMUTABLE;			//gpu만 read only
	desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	desc.ByteWidth = (uint32)(sizeof(Vertex) * _vertices.size());

	D3D11_SUBRESOURCE_DATA data;
	ZeroMemory(&data, sizeof(data));
	data.pSysMem = _vertices.data();			//첫번째 시작주소 cpu값이 복사된다.

	_device->CreateBuffer(&desc,&data, _vertexBuffer.GetAddressOf());
}

 

 

이제 입력에 대한 정보를 명시해줘야한다. 

void Game::CreateInputLayout()
{
	//입력에 대한 정보 ~바이트부터 뛰면 칼러인지 
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0},
		{"COLOR",0,DXGI_FORMAT_R32G32B32A32_FLOAT,0,12,D3D11_INPUT_PER_VERTEX_DATA,0}
	};

	const int32 count = sizeof(layout)/sizeof(D3D11_INPUT_ELEMENT_DESC);
	_device->CreateInputLayout(layout,count,);
}

 

하지만 CreateInputLayout 함수의 매개변수에는 쉐이더에 관한 변수가 필요하다. 

그럼 쉐이더를 만들어주자 

쉐이더 -> 관련 코드를 넣어두는 하나의 파일

 

필터를 새로만들고 HLSL파일을 만들어보자 

쉐이더파일 생성

 

쉐이더

-> 1. 먼저 컴파일/ 빌드해서 cso파일을 사용 2.실행 순간에 코드를 읽어서 컴파일 -> 동적으로 사용

->먼저 만들어진걸 사용하는게 좋다. -> 문법적인 /오타 같은 오류를 컴파일 단계에서 알 수 있다. 

 

쉐이더 입력으로 들어오는 구조체부터 선언해주자 이때 InputLayout과 이름을 맞춰주자

struct VS_INPUT
{
    float4 position : POSITIONT;
    float4 color : COLOR;
};

 

쉐이더관련 코드를 완성해보자

정점 쉐이더와 픽셀 쉐이더 단계에 필요한 함수를 추가해준다.

struct VS_INPUT
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct VS_OUTPUT
{
    float4 position : SV_POSITION;      //시스템 VALUE  무조건 있어야한다.
    float4 color : COLOR;
};

//정점 쉐이더- 위치관련 -> 레스터라이저-정점바탕으로 도형만들고 내/외부 판단 및 보간
VS_OUTPUT VS(VS_INPUT input)
{
    VS_OUTPUT output;
    output.position = input.position;
    output.color = input.color;
    
    return output;
}

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    
    
    return float4(1, 0, 0, 0);
}

 

그리고 이제 이 쉐이더를 가져올 함수를 만들자

만들기전에 정점/ 픽셀 쉐이더 관련 변수를 추가해준다. 

	//VS
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
	ComPtr<ID3DBlob> _vsBlob = nullptr;
	//PS
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
	ComPtr<ID3DBlob> _psBlob = nullptr;
void Game::LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
	const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;		//디버그 최적화 건너뛰기
	HRESULT hr = ::D3DCompileFromFile(
		path.c_str(),
		nullptr,
		D3D_COMPILE_STANDARD_FILE_INCLUDE,
		name.c_str(),
		version.c_str(),
		compileFlag,
		0,
		blob.GetAddressOf(),
		nullptr
	);

	CHECK(hr);
}

 

파일에 있던걸 가져와서 어떻게 작동하게 할지 건내줘야해서 가져오는 함수를

VS와 PS에서도 해야하기 때문에 관련 함수를 만들어 준다. 

void Game::CreateVS()
{
	LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob);

	HRESULT hr = _device->CreateVertexShader(_vsBlob->GetBufferPointer(),
		_vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
	CHECK(hr);
}

void Game::CreatePS()
{
	LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob);

	HRESULT hr = _device->CreatePixelShader(_psBlob->GetBufferPointer(),
		_psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
	CHECK(hr);
}

 

 

이제  Init 부분에 만든 함수들을 실행시키도록 넣어준다 .

 

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;


	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();

	//삼각형 그리기 파트
	CreateGeometry();
	CreateVS();
	CreateInputLayout();
	CreatePS();
}

 

 

그리고 실제로 그리기 부분은 렌더부분에서 이루어 지기때문에 파이프라인 단계에 맞게 채워준다.

void Game::Render()
{
	RenderBegin();			//초기화

	// IA - VS - RS - PS -OM
	//TODO : 그리기
	{
		uint32 stride = sizeof(Vertex);
		uint32 offset = 0;
		//IA - 세팅부분
		_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(),&stride, &offset);
		_deviceContext->IASetInputLayout(_inputLayout.Get());
		_deviceContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);		//삼각형으로 만들어주기

		//VS
		_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);		//이걸로 일하게 

		//RS

		//PS
		_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
		//OM

		_deviceContext->Draw(_vertices.size(), 0);
	}

	RenderEnd();			//제출
}

 

실행화면 

 

 

만약 PS 단계에서 입력의 색을 따라 만들고 세 점을 RGB 하나씩 가지고 있도록 한다면 

 

//모든 픽셀단위 대상 - 색상관련
float4 PS(VS_OUTPUT input) : SV_Target  //랜더 타켓하는 곳으로 보내주기
{
    
    
    return input.color;
}

 

이런식으로 그라데이션 삼각형이 나오게 된다. 

-> 보간이 잘 이루어졌다. 

 

정점 / 자원 -> cpu만 가지고 있었는데 createVertexBuffer을 통해 gpu로 복사가 이루어진다.

gpu도 해당 자원을 가지고 있게 된다. 

IA - createVertexBuffer를 통해 자원을 사용하겠다는 의미

VS- 정점을 gpu에서 연산 처리 -> 정점에 대한 정보가 확정되어서 나가게 된다.

Rasterize- 삼각형있을 때 내/외부 판단, 보간 진행

PS- 모든 픽셀에 대한 색상 - 빛 / 그림자

랜더타켓 - 어디에다 그려줘

 

VS,PS- 세부적인 옵션을 건드릴 수 있도록하는 툴로 만들면 -> 머테리얼 

 

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11][C++]4. 텍스처와 UV  (0) 2024.07.01
[Directx11]3.장치초기화  (0) 2024.06.27
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (0) 2024.06.25
[Directx11]2.랜더링 파이프라인2  (0) 2024.06.21

https://www.youtube.com/watch?v=-5jCh22feJg&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=20

1. 인벤토리 카테고리에 해당하는 텍스트 만들어주기 

각 칸의 카테고리에 해당하는 텍스트를 옆에 붙여준다.

 

2. 활, 방패같은 장비는 등에 먼저 보이도록 로직 수정 

일단 활과 방패의 fbx 파일을 import 해주자 

https://sketchfab.com/3d-models/medieval-shield-b98b8f64d935415aab0fe9b70074511f#download

 

Medieval Shield - Download Free 3D model by Artem Mykhailov - Sketchfab

Artstation post: https://www.artstation.com/artwork/e012eY Based on the concept by Artyom Vlaskin: https://artyomvlaskin.cgsociety.org/okkw/928308

sketchfab.com

https://sketchfab.com/3d-models/wooden-bow-free-a762abcffc27478caf19dfaac086485d#download

 

Wooden Bow Free - Download Free 3D model by Red_Ilya - Sketchfab

Not the strongest bow, but when there is nothing at hand, it is the best

sketchfab.com

 

Fbx파일을 다운 받은 뒤 import 해주고 텍스처 설정을 해줘야한다. 

Shield 텍스처 설정
Bow 텍스처 설정

 

캐릭터 매쉬 골격에 소캣 추가해주기

등에 활과 방패가 들어올 수 있도록 소캣을 추가해준다. 

소캣
결과

이제 실제 매쉬에 스태틱 매쉬 컴포넌트를 추가해서 실제로 적용될 수 있도록 한다. 

스태틱매쉬를 만든 뒤 부모소캣을 위에서 만들어둔 소캣으로 적용하면 된다.

 

소캣 설정

 

이제 만든 매쉬에 들어갈 수  있도록 커스텀 이벤트를 만들어주자 

그리고 들어오는 아이템 타입에 따라 다른 이벤트가 호출되도록 스위치문을 구성해준다. 현재 아머는 없어서 그냥 스트링만 출력하도록 했다.

이벤트 구성 및 흐름도

 

DB 테이블에 방패를 추가해주고 스태틱매쉬와 소캣을 맞게 수정해준다.

DB테이블

 

결과화면

 

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

 

문제가 정말 길다. 

하지만 우리가 봐야할 것은

입력에서 주어지는 나열된 상자를 보면 된다.

이 상자를 순회하면서 문제에서 주어진 방식대로 상자 안의 숫자로 계속 상자를 방문처리하면서 그룹을 나누고 만약 그룹이 2개보다 작다면 0점이고 아니라면 그룹 수 중에 제일 많은 수와 그 다음으로 많은 수를 곱해서 반환해주면 된다. 

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

using namespace std;

// 특정 시작점에서 그룹의 크기를 찾는 함수
int find_group(int start, vector<int>& cards, vector<bool>& visited) {
    int group_size = 0;  // 그룹 크기 초기화
    int current = start;  // 현재 상자를 시작 상자로 설정
    while (!visited[current]) {  // 현재 상자가 방문되지 않은 동안 반복
        visited[current] = true;            //방문처리 
        current = cards[current] - 1;  // 상자 안의 숫자를 통해 다음 상자 선택
        group_size += 1;  // 그룹 크기를 증가시킴
    }
    return group_size;  
}

int solution(vector<int> cards) {
    int n = cards.size();  // 상자의 수
    vector<bool> visited(n, false);  // 방문 여부를 저장
    vector<int> group_sizes;  // 각 그룹의 크기를 저장

    // 모든 상자에 대해 그룹을 찾음
    for (int i = 0; i < n; ++i) {
        if (!visited[i]) {  // 현재 상자가 방문되지 않았다면
            int group_size = find_group(i, cards, visited);  // 그룹의 크기를 찾음
            group_sizes.push_back(group_size);  // 그룹 크기를 벡터에 추가
        }
    }

    // 그룹 크기를 내림차순으로 정렬
    sort(group_sizes.rbegin(), group_sizes.rend());

    // 최대 점수를 계산
    if (group_sizes.size() < 2) {  // 그룹이 2개 미만이면
        return 0;  // 점수는 0
    } else {
        return group_sizes[0] * group_sizes[1];  // 가장 큰 두 그룹의 크기를 곱한 값이 최대 점수
    }
}

 

 

함수,인자가 엄청 많기 때문에

다이렉트 11을 공부할 때는 나무보다는 숲을 봐야한다. 

옵션 - 특이점이 있는지만 파악하기 

틀을 공부하자 

 

유용한 팁 

// I: 인터페이스 Com객체-> 생성은 함수의 결과 값 제거-> release 
ID3D11Device* _device; 
ID3D11DeviceContext* _deviceContext;

이거보다는 

ComPtr<ID3D11Device> _device; 자동으로 관리해주는 스마트 포인터가 좋다

 

//Ref카운트조절 코드로 관리-> 안좋다
_device->AddRef();

_device->Release();

 

모두 다른 gpu연동, 관리는 어떻게? -> Com 인터페이스 

ComPtr<ID3D11Device> _device; -> 유닛생산
ComPtr<ID3D11DeviceContext> _deviceContext; -> 유닛에 일시키기

 

궁금한건 구글링하며 찾아보자 

	//DXGI : DX와는 독립적으로 하위 수준 작업 관리 => 시스템,하드웨어 통신
	//스왑체인-> 그리는 것과 보여주는 것 따로 해야 보통 방식 ->전면 후면 고속복사
	ComPtr<IDXGISwapChain> _swapChain = nullptr;

 

	_device.Get();			//내부적 디바이스 ID3D11Device를 가져오고 싶을때
	_device.GetAddressOf();		//그값의 주소값
	//HRESULT 일종의 bool값
	HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
		nullptr,
		D3D_DRIVER_TYPE_HARDWARE,			//어떤걸 쓸건지 -> gpu or software로 gpu
		nullptr,
		0,
		nullptr,				//지원하는 dx 버전 레벨 기본값은 내가 지원할 수 있는 상위버전 골라줌 
		0,						//위에 배열 크기
		D3D11_SDK_VERSION,				//매크로
		&desc,
		_swapChain.GetAddressOf(),			//**이면 주소값 가져오는 GetAddressOf()
		_device.GetAddressOf(),
		nullptr,
		_deviceContext.GetAddressOf()
	);
	//성공했는지 체크해준다.
	assert(SUCCEEDED(hr));


오늘 완성 코드

Game.h

#pragma once


class Game
{
public:
	Game();
	~Game();
public:
	void Init(HWND hwnd);		//윈도우 핸들받아줌
	void Update();
	void Render();
private:
	/// <summary>
	/// 그리기 함수
	/// </summary>
	void RenderBegin();		
	void RenderEnd();
private:
	void CreateDeviceAndSwapChain();
	/// <summary>
	/// 버퍼를 묘사 Tag를 달아서 GPU에 알려주기 위함
	/// </summary>
	void CreateRenderTargetView();
	/// <summary>
	/// 뷰포트 묘사 
	/// </summary>
	void SetViewport();
private:
	HWND _hwnd;
	uint32 _width = 0;
	uint32 _height = 0;

private:
	//Device & SwapChain
	// I: 인터페이스 Comptr -> 스마트 포인터- 자동관리해줌, wrl에 있다.
	ComPtr<ID3D11Device> _device; 
	ComPtr<ID3D11DeviceContext> _deviceContext;
	//DXGI : DX와는 독립적으로 하위 수준 작업 관리 => 시스템,하드웨어 통신
	//스왑체인-> 그리는 것과 보여주는 것 따로 해야 보통 방식 ->전면 후면 고속복사
	ComPtr<IDXGISwapChain> _swapChain = nullptr;

	///RTV 렌더타켓뷰
	ComPtr<ID3D11RenderTargetView> _renderTargetView;

	//Misc 
	D3D11_VIEWPORT _viewport = { 0 };
	float _clearColor[4] = { 0.5f,0.5f,0.5f,0.5f };
	
};

 

 Game.cpp

#include "pch.h"
#include "Game.h"

Game::Game()
{
}

Game::~Game()
{
}

void Game::Init(HWND hwnd)
{
	_hwnd = hwnd;
	_width = GWinSizeX;
	_height = GwinSizeY;

	//TODO
	CreateDeviceAndSwapChain();
	CreateRenderTargetView();
	SetViewport();
}

void Game::Update()
{

}

void Game::Render()
{
	RenderBegin();			//초기화

	//TODO : 그리기


	RenderEnd();			//제출
}


void Game::RenderBegin()		//랜더링 파이프라인에 우리가 만든거 묶어주기
{
	_deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);				//OM: OUTPUT Mege
	_deviceContext->ClearRenderTargetView(_renderTargetView.Get(), _clearColor);		//색상으로 초기화해주기
	_deviceContext->RSSetViewports(1, &_viewport);
}

void Game::RenderEnd()
{
	HRESULT hr = _swapChain->Present(1, 0);			//제출 전면 <= 후면 
	CHECK(hr);
}

void Game::CreateDeviceAndSwapChain()
{
	DXGI_SWAP_CHAIN_DESC desc;
	ZeroMemory(&desc, sizeof(desc));		//다 0으로 초기화해줌 필요한 것만 따로 초기화
	{
		desc.BufferDesc.Width = _width;		//버퍼도 같은 크기로 800 x 600
		desc.BufferDesc.Height = _height;
		desc.BufferDesc.RefreshRate.Numerator = 60;			//화면 주사율
		desc.BufferDesc.RefreshRate.Denominator = 1;
		desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
		desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
		desc.SampleDesc.Count = 1;			//멀티 샘플링 계단현상, 찌그러짐 어떻게 처리할지 
		desc.SampleDesc.Quality = 0;
		desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;			//버퍼를 어떻게 쓸건지 - 그려주는 역활
		desc.BufferCount = 1;		//버퍼몇개
		desc.OutputWindow = _hwnd;		// 결과 윈도우핸들
		desc.Windowed = true;
		desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
	}
	//HRESULT 일종의 bool값
	HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
		nullptr,
		D3D_DRIVER_TYPE_HARDWARE,			//어떤걸 쓸건지 -> gpu or software로 gpu
		nullptr,
		0,
		nullptr,				//지원하는 dx 버전 레벨 기본값은 내가 지원할 수 있는 상위버전 골라줌 
		0,						//위에 배열 크기
		D3D11_SDK_VERSION,				//매크로
		&desc,
		_swapChain.GetAddressOf(),			//**이면 주소값 가져오는 GetAddressOf()
		_device.GetAddressOf(),
		nullptr,
		_deviceContext.GetAddressOf()
	);

	CHECK(hr);
}

void Game::CreateRenderTargetView()
{
	HRESULT hr;

	ComPtr<ID3D11Texture2D> backBuffer = nullptr;
	hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf());		//백버퍼 텍스처(PNG)로 가져오기
	CHECK(hr);

	//택스처라는건 그냥 쓰면 아무것도 없는데 거기에 최종 그림을 그리는 곳에 쓸거라는 태그를 달아주는 것 
	_device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargetView.GetAddressOf());  //렌더타켓뷰라는것으로 텍스처를 명시해 만들어준다.
	CHECK(hr);
	
}

void Game::SetViewport()
{
	_viewport.TopLeftX = 0.f;
	_viewport.TopLeftY = 0.f;
	_viewport.Width = static_cast<float>(_width);
	_viewport.Height = static_cast<float>(_height);
	_viewport.MaxDepth = 0.f;
	_viewport.MaxDepth = 1.f;
}

 

오늘의 결과화면

함수 단위로 크게크게 보자

'게임공부 > Directx11' 카테고리의 다른 글

[Directx11][C++]4. 텍스처와 UV  (0) 2024.07.01
[Directx11]4. 삼각형 그리기  (1) 2024.07.01
[Directx11]2.초기설정  (0) 2024.06.26
[Directx11]그래픽스OT  (0) 2024.06.25
[Directx11]2.랜더링 파이프라인2  (0) 2024.06.21

https://www.youtube.com/watch?v=Dh5ty_W8Ayg&list=PLiSlOaRBfgkcPAhYpGps16PT_9f28amXi&index=19

오늘은 아이템슬롯에 클릭했을 때 무기를 장비할 수 있게하는 걸 구현해보자 

 

1. 캐릭터 이벤트그래프에서 커스텀 이벤트 추가

sword를 Equipped Weapon으로 바꿔서 이것의 static mesh를 바꿔주는 방식으로 만들 것이다.

선택한 아이템을 DB에서 찾아서 이에 해당하는  static mesh를 가져와서 그것으로 기존 weapon의 mesh를 바꿔준다. 

 

2. 기존 아이템슬롯 assign 이벤트 마지막에 1의 이벤트 추가

1번에서 만든 이벤트를 아이템 클릭할 때 작동할 수 있도록 assign item에서 추가해준다. 

 

3. 크기가 다른 무기를 Socket으로 맞춰주기 

기존 무기 구조체에서 변수를 하나 추가해서 Socket에 맞게 들어가도록 해주었다.

 

4. 카테고리 만들어주기 

각 아이템슬롯 자체에 기존에 만들어둔 카테고리 변수를 추가해서 어떤 아이템슬롯인지 전달할 수 있도록 한다.

그리고 Construct 될때가 아니라 custom event로 호출할 때만 아이템 목록이 뜨도록 한다. 

그리고 과정 중에 해당 카테고리만 가져오도록 bool 조건과 branch를 사용하여 준다.

카테고리에 해당하는 무기를 가져오는 과정에서 기존에 있던 목록이 계속 중첩되어 뜰 수 도 있어서 장비UI의 Open 이벤트에서 Item Vertical Box의 Children을 clear 해준다. 

 

오늘의 결과화면 

+ Recent posts