전체 글 (100)

  • 2022.01.06
  • 2022.01.04
  • 2022.01.04
  • 2022.01.03
  • 2022.01.03
  • 2021.12.31
  • 2021.12.30
  • 2021.12.28
  • 2021.12.28
  • 2021.12.28
  • 2021.12.27
  • 2021.12.24
  • 01
    06

    프로세스(Process)

     

    컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램이다.

    Code, Data, Stack, Heap의 구조로 되어 있는 독립된 메모리 영역이다.

     

    Code 텍스트, 코드영역
    Data 전역변수, 정적변수, 배열, 구조체
    Heap 동적 데이터 (new delete)
    Stack 지역변수, 매개변수, 임시메모리

     

    쓰레드(Thread)

     

    CPU 코어에서 돌아가는 프로그램 단위를 쓰레드(Thread)라고 한다.

    한 개의 프로세스에는 최소 한개의 쓰레드로 돌아가며, 프로세스에서

    여러개의 쓰레드로 구성하는걸 멀티 쓰레드라고 한다.

     

     

    하나의 프로세스에서 여러 쓰레드에서 나누면

    자원을 효율적으로 관리 할 수 있고, 응답시간이 단축된다는 장점이 있지만

    (쓰레드를 사용하면 일의 분업화가 된다. )

    단점으로는 동기화 문제가 있다.

     

    동기화 문제

    쓰레드A와 쓰레드B로 왔다 갔다(스위칭)하면서 경쟁하는데

    둘의 조건이 안맞아 쓰레드끼리 서로 대기하는 교착상태(DeadLock)이 발생한다.

    실행 순서를 제어해야하는데, 이런 기술들을 동기화(Synchronization)라고 한다. 

     

    쓰레드 스케줄링 -> 쓰레드를 얼마 만큼 간격으로 실행할걱인가 결정하는 정책

    스위칭 -> 스레드 A는 일정시간(0.02초) 퀀텀시간동안 실행하고 잠시 중지하고 스레드 B로 스위칭한다.

    스레드 컨텍스트 -> 쓰레드 스위칭간에 작업상태 정보를 저장해 놓는 것


    클라이언트/서버 멀티쓰레드 C++ 채팅 프로그램

    c++에서 쓰레드를 생성하는 방법은 두가지가 있다.

    1. Window API 쓰레드

    2. C++11부터 지원하는 쓰레드. 

     

    C++11 부터 지원하는 쓰레드 방식으로 생성한다.

    //C++
    HANDLE CreateThread(
      [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,
      [in]            SIZE_T                  dwStackSize,
      [in]            LPTHREAD_START_ROUTINE  lpStartAddress,
      [in, optional]  __drv_aliasesMem LPVOID lpParameter,
      [in]            DWORD                   dwCreationFlags,
      [out, optional] LPDWORD                 lpThreadId
    );

     

     lpThreadAttributes, // 보안 속성 지정
     dwStackSize,         // 스택 크기를 지정
     lpStartAddress,      // 스레드의 시작함수를 지정
     lpParameter,         //시작 함수의 인자값
     dwCreationFlags,   // 스레드의 특성 지정 (바로 시작 OR 대기상태)
     lpThreadId           // 스레드의 ID를 반환함

    반환값으로는 스레드 핸들값을 반환한다.

    예시

    패킷 전송하는 스레드와 패킷을 받아오는 스레드

     

    스레드를 종료하는 방법은 함수안에서, 밖에서 하는 함수가 있지만

    제일 좋은 방법은 시작함수에서 반환하는 것이라고 한다.

     

    스래드 중지 및 재실행

    // 해당 스레드의 동작을 일시 중지 시킨다.
    DWORD SuspendThread(HANDLE hThread);
    // 일시 정지된 스레드의 수행을 재개한다.
    DWORD ResumeThread(HANDLE hThread);

     

     

    더보기

    클라이언트

    #define ServerIP "192.xxx.0.xx"
    #define ServerIP2 "192.xxx.0.xx"
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <conio.h>
    #include "KPacket.h"
    #pragma comment	(lib, "ws2_32.lib")
    int SendMsg(SOCKET sock, char* msg, WORD type);
    int SendPacket(SOCKET sock, char* msg, WORD type);
    int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket);
    int RecvPacketData(SOCKET sock, UPACKET& recvPacket);
    
    DWORD WINAPI SendThread(LPVOID IpThreadParameter);
    DWORD WINAPI RecvThread(LPVOID IpThreadParameter);
    //SendThread가 리턴이 되면 쓰레드가 종료가 된다.
    //클라이언트
    void main()
    {
    	/*WSACleanup 함수와 쌍을 이뤄 소켓 프로그램의 시작과 끝을 나타냄*/
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	//TCP - SOCK_STREAM , UDP - SOCK_DGRAM
    
    	SOCKET sock = socket(AF_INET,
    		SOCK_STREAM, 0);
    
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = inet_addr(ServerIP);
    	int iRet = connect(
    		sock,
    		(sockaddr*)&sa,
    		sizeof(sa));
    	if (iRet == SOCKET_ERROR)
    	{
    		return;
    	}
    	std::cout << "접속성공!" << std::endl;
    
    	//논블로킹 소켓 0이 아니라면 논블로킹 모드로 설정
    	u_long on = 1;
    	ioctlsocket(sock, FIONBIO, &on);
    
    	//쓰레드 생성에는 두가지 방법이 있음
    	// Window API 방법, C++ 11 방법, :: 범위해결자
    	//c++
    	DWORD ThreadIndex;
    	HANDLE hThread_Send = ::CreateThread(0, 0, SendThread,
    		(LPVOID)sock, 0, &ThreadIndex);
    
    	HANDLE hThread_Recv = ::CreateThread(0, 0, RecvThread,
    		(LPVOID)sock, 0, &ThreadIndex);
    
    	//메인 쓰레드 작업
    	while (1)
    	{
    		Sleep(100);
    	}
    	std::cout << "접속종료" << std::endl;
    
    	CloseHandle(hThread_Send); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
    	CloseHandle(hThread_Recv); //쓰레드 종료가 아닌 제어를 운영체제에게 넘겨주는 것
    	closesocket(sock);
    	WSACleanup();
    }
    
    DWORD __stdcall RecvThread(LPVOID IpThreadParameter)
    {
    	SOCKET sock = (SOCKET)IpThreadParameter;
    	while (1)
    	{
    		UPACKET packet;
    		int iRet = RecvPacketHeader(sock, packet);
    		if (iRet < 0) break;
    		if (iRet == 1)
    		{
    			int iRet = RecvPacketData(sock, packet);
    			if (iRet < 0) break;
    			if (iRet == 0) continue;
    			// 메세지 처리
    			KPacket data;
    			data.m_uPacket = packet;
    			KChatting recvdata;
    			ZeroMemory(&recvdata, sizeof(recvdata));
    			data >> recvdata.index >> recvdata.name
    				>> recvdata.damage >> recvdata.message;
    			std::cout << "\n" <<
    				"[" << recvdata.name << "]"
    				<< recvdata.message;
    		}
    	}
    	return 0;
    }
    
    DWORD __stdcall SendThread(LPVOID IpThreadParameter)
    {
    	SOCKET sock = (SOCKET)IpThreadParameter;
    	char szBuffer[256] = { 0, };
    	while (1)
    	{
    		ZeroMemory(szBuffer, sizeof(char) * 256);
    		fgets(szBuffer, 256, stdin);
    		if (strlen(szBuffer) == 0)
    		{
    			std::cout << "정상 종료됨.." << std::endl;
    			break;
    		}
    
    		// 방법 1
    		//int iSendByte = SendMsg(sock, szBuffer, PACKET_CHAT_MSG);
    		// 방법 2
    		int iSendByte = SendPacket(sock, szBuffer, PACKET_CHAT_MSG);
    		if (iSendByte < 0)
    		{
    			std::cout << "비정상 종료됨.." << std::endl;
    			break;
    		}
    	}
    	return 0;
    }
    
    
    
    int SendMsg(SOCKET sock, char* msg, WORD type)
    {
    	// 1번 패킷 생성
    	UPACKET packet;
    	ZeroMemory(&packet, sizeof(packet));
    	//메세지 내용에서 헤더 사이즈 추가
    	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
    	packet.ph.type = type;
    	memcpy(packet.msg, msg, strlen(msg));
    	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트), recvbuffer
    	char* pMsg = (char*)&packet;
    	int iSendSize = 0;
    	do {
    		int iSendByte = send(sock, &pMsg[iSendSize],
    			packet.ph.len - iSendSize, 0);
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    		iSendSize += iSendByte;
    	} while (iSendSize < packet.ph.len);
    	return iSendSize;
    }
    //패킷 전송
    int SendPacket(SOCKET sock, char* msg, WORD type)
    {
    
    	// 1번 패킷 생성
    	KPacket tPacket(type);
    
    	//index, name[20], damage, message[256]
    	tPacket << 999 << "최지원" << (short)50 << msg;
    	
    	//생성자로 패킷 생성자로 패킷을 받아 패킷을 만듬
    	KPacket tPacketTest(tPacket);
    
    	KChatting recvdata;
    	ZeroMemory(&recvdata, sizeof(recvdata));
    	
    	//패킷에서 정보 파싱
    	tPacketTest >> recvdata.index >> recvdata.name
    		>> recvdata.damage >> recvdata.message;
    
    	char* pData = (char*)&tPacket.m_uPacket;
    
    	int iSendSize = 0;
    	do {
    		int iSendByte = send(sock, &pData[iSendSize],
    			tPacket.m_uPacket.ph.len - iSendSize, 0);
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    		iSendSize += iSendByte;
    	} while (iSendSize < tPacket.m_uPacket.ph.len);
    	return iSendSize;
    }
    //패킷 헤더만 받아오기
    int RecvPacketHeader(SOCKET sock, UPACKET& recvPacket)
    {
    	char szRecvBuffer[256] = { 0, };
    	//패킷헤더 받기
    	ZeroMemory(&recvPacket, sizeof(recvPacket));
    	bool bRun = true;
    	int iRecvSize = 0;
    	//페킷 헤더 (4바이트)가 될때까지 do while문 
    	do {
    		int iRecvByte = recv(sock, szRecvBuffer,
    			PACKET_HEADER_SIZE, 0);
    		iRecvSize += iRecvByte;
    		if (iRecvByte == 0)
    		{
    			closesocket(sock);
    			std::cout << " 접속종료됨." << std::endl;
    			return -1;
    		}
    		if (iRecvByte == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << " 비정상 접속종료됨." << std::endl;
    				return -1;
    			}
    			else
    			{
    				return 0;
    			}
    		}
    	} while (iRecvSize < PACKET_HEADER_SIZE);
    	memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
    	return 1;
    }
    //패킷 헤더 제외한 패킷 받기
    int RecvPacketData(SOCKET sock, UPACKET& recvPacket)
    {
    	// 데이터 받기
    	int iRecvSize = 0;
    	do {
    		int iRecvByte = recv(sock, recvPacket.msg,
    			recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
    		iRecvSize += iRecvByte;
    		if (iRecvByte == 0)
    		{
    			closesocket(sock);
    			std::cout << " 접속종료됨." << std::endl;
    			return -1;
    		}
    		if (iRecvByte == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << " 비정상 접속종료됨." << std::endl;
    				return -1;
    			}
    			else
    			{
    				return 0;
    			}
    		}
    	} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);
    	return 1;
    }

    서버

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <list>
    #include <string>
    #include "KPacket.h"
    #pragma comment	(lib, "ws2_32.lib")
    
    //서버
    struct User
    {
    	SOCKET		m_Sock;
    	SOCKADDR_IN m_Addr;
    	std::string m_csName;
    	short       m_iPort;
    	void set(SOCKET sock, SOCKADDR_IN addr)
    	{
    		m_Sock = sock;
    		m_Addr = addr;
    		//네트워크 주소 변환 함수 빅엔디안(32bit) ->  
    		m_csName = inet_ntoa(addr.sin_addr);
    		//엔디안은 메모리 연속된 대상을 배열하는 방법으로 
    		// Network Byte 순서를 To Host의 Byte 순서로 바꿈
    		m_iPort = ntohs(addr.sin_port);
    	}
    };
    
    int SendMsg(SOCKET sock, char* msg, WORD type)
    {
    	//1. 패킷을 객체 생성
    	UPACKET packet;
    	ZeroMemory(&packet, sizeof(packet));
    	//패킷의 길이는 헤더 사이즈 + 메세지 사이즈
    	packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
    	packet.ph.type = type;
    	//메세지 받은걸로 memcpy로 패킷 만듬
    	memcpy(packet.msg, msg, strlen(msg));
    
    	// 2번 패킷 전송 : 운영체제 sendbuffer(short바이트)
    	char* pMsg = (char*)&packet;
    	int iSize = 0;
    
    	do {
    		//TCP는 연속적이지만 바이트 단위로 나눠져 보내질도 있음
    		int iSendByte = send(sock, &pMsg[iSize],
    			packet.ph.len - iSize, 0);
    
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    
    		iSize += iSendByte;
    	} while (iSize < packet.ph.len);
    	return iSize;
    }
    //패킷을 받았을때.
    int SendMsg(SOCKET sock, UPACKET& packet)
    {
    	char* pMsg = (char*)&packet;
    	int iSize = 0;
    	do {
    		int iSendByte = send(sock, &pMsg[iSize],
    			packet.ph.len - iSize, 0);
    
    		if (iSendByte == SOCKET_ERROR)
    		{
    			if (WSAGetLastError() != WSAEWOULDBLOCK)
    			{
    				return -1;
    			}
    		}
    
    		iSize += iSendByte;
    	} while (iSize < packet.ph.len);
    	return iSize;
    }
    
    void main()
    {
    	WSADATA wsa;
    	//2.2 버젼 WSA 
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, 0);
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = htonl(INADDR_ANY);
    	//소켓에 주소 할당
    	int iRet = bind(ListenSock, (sockaddr*)&sa, sizeof(sa));
    	if (iRet == SOCKET_ERROR)  return;
    	//클라이언트로 부터 연결 요청을 처리할수있는 상태를 만듬 
    	iRet = listen(ListenSock, SOMAXCONN);
    	if (iRet == SOCKET_ERROR)  return;
    
    	SOCKADDR_IN clientAddr;
    	int iLen = sizeof(clientAddr);
    
    	std::cout<< "Server Start." << std::endl;
    
    	//non blocking socket 0이면 블락킹 소켓
    	u_long on = 1;
    	ioctlsocket(ListenSock, FIONBIO, &on);
    
    	std::list<User> userlist;
    
    	while (1)
    	{
    		SOCKET clientSock = accept(ListenSock,
    			(sockaddr*)&clientAddr, &iLen);
    
    		if (clientSock == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << "ErrorCode=" << iError << std::endl;
    				break;
    			}
    		}
    		//클라이언트가 접속 시 시작함
    		else
    		{
    			User user;
    			user.set(clientSock, clientAddr);
    			userlist.push_back(user);
    			std::cout
    				<< "ip =" << inet_ntoa(clientAddr.sin_addr)
    				<< "port =" << ntohs(clientAddr.sin_port)
    				<< "  " << std::endl;
    			u_long on = 1;
    			ioctlsocket(clientSock, FIONBIO, &on);
    			std::cout << userlist.size() << " 명 접속중." << std::endl;
    		}
    
    		if (userlist.size() > 0)
    		{
    			std::list<User>::iterator iter;
    			for (iter = userlist.begin(); iter != userlist.end(); )
    			{
    				User user = *iter;
    				char szRecvBuffer[256] = { 0, };
    				//패킷헤더 받기
    				UPACKET recvPacket;
    				ZeroMemory(&recvPacket, sizeof(recvPacket));
    				int iRecvSize = 0;
    				do {
    					int iRecvByte = recv(user.m_Sock, szRecvBuffer,
    						PACKET_HEADER_SIZE, 0);
    					iRecvSize += iRecvByte;
    					if (iRecvByte == 0)
    					{
    						closesocket(user.m_Sock);
    						iter = userlist.erase(iter);
    						std::cout << user.m_csName << " 접속종료됨." << std::endl;
    						break;
    					}
    					if (iRecvByte == SOCKET_ERROR)
    					{
    						int iError = WSAGetLastError();
    						if (iError != WSAEWOULDBLOCK)
    						{
    							iter = userlist.erase(iter);
    							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    							break;
    						}
    						else
    						{
    							break;
    						}
    					}
    				} while (iRecvSize < PACKET_HEADER_SIZE);
    
    				if (iRecvSize == SOCKET_ERROR)
    				{
    					if (iter != userlist.end())
    					{
    						iter++;
    					}
    					continue;
    				}
    
    				memcpy(&recvPacket.ph, szRecvBuffer, PACKET_HEADER_SIZE);
    				// 데이터 받기
    				iRecvSize = 0;
    				do {
    					int iRecvByte = recv(user.m_Sock, recvPacket.msg,
    						recvPacket.ph.len - PACKET_HEADER_SIZE - iRecvSize, 0);
    					iRecvSize += iRecvByte;
    					if (iRecvByte == 0)
    					{
    						closesocket(user.m_Sock);
    						iter = userlist.erase(iter);
    						std::cout << user.m_csName << " 접속종료됨." << std::endl;
    						continue;
    					}
    					if (iRecvByte == SOCKET_ERROR)
    					{
    						int iError = WSAGetLastError();
    						if (iError != WSAEWOULDBLOCK)
    						{
    							iter = userlist.erase(iter);
    							std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    						}
    						else
    						{
    							iter++;
    						}
    					}
    				} while (iRecvSize < recvPacket.ph.len - PACKET_HEADER_SIZE);
    
    				KPacket data;
    				data.m_uPacket = recvPacket;
    				KChatting recvdata;
    				ZeroMemory(&recvdata, sizeof(recvdata));
    				data >> recvdata.index >> recvdata.name
    					>> recvdata.damage >> recvdata.message;
    
    				std::cout << "\n" <<
    					"[" << recvdata.name << "]"
    					<< recvdata.message;
    
    				// 패킷 완성		
    				std::list<User>::iterator iterSend;
    				for (iterSend = userlist.begin();
    					iterSend != userlist.end(); )
    				{
    					User user = *iterSend;
    					int iSendMsgSize = SendMsg(user.m_Sock, recvPacket);
    					if (iSendMsgSize < 0)
    					{
    						closesocket(user.m_Sock);
    						iterSend = userlist.erase(iterSend);
    						std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    					}
    					else
    					{
    						iterSend++;
    					}
    				}
    				if (iter != userlist.end())
    				{
    					iter++;
    				}
    			}
    		}
    	}
    	closesocket(ListenSock);
    	WSACleanup();
    }
    COMMENT
     
    01
    04

    https://youtu.be/Wh0xuvLOYKg


    https://youtu.be/QDjcYHbZtF0


    본 프로젝트는 반려동물이 없는 예비 반려동물 주인이나, 뷰니멀들에게 반려묘 육성을 체
    험해볼 수 있는 콘텐츠 제작과정을 설명하는 논문이다. 닌텐독스의 기기와 안드로이드 기
    기의 큰 차이점은 안드로이드 기기는 스마트폰으로써 다양한 모듈이 탑재되어 있다는 점이
    다. 

    예를 들어 고화질 카메라는 증강현실 구현에 있어서 영상 합성 기술을 적용할 수 있게
    하였다. 증강현실에는 대표적으로 마카 인식 기술과 영상 합성 기술이 있는데, 마카 인식은
    높은 정확성을 가지지만 마커가 없으면 활용할 수 없다는 단점이 있다. 고화질 카메라로
    이를 보완하게 되었다. 

    아래 사진은 각 각 영상합성 기술을 사용하여 AR시연 모습,

    근거리 유저 간 블루투스 통신 플레이의 시연 모습이다.

     

    1) AR 기능
    AR 기술에는 대표적으로 마카인식 기술과 영상합성 기술이 있다. 마커 인식은 높은 정확성을 가지지
    만 마커가 없으면 활용할 수 없는 단점이 있다. 이를 보완하기 위해 영상합성 기술을 채택했다. Google의
    AR Foundation 프레임워크를 활용해 AR을 구현하였다. 장신구를 착용, 다양한 모션들로 고양이와 사진
    을 찍어 추억을 남기는 콘텐츠로 제작하였다.
    AR Foundation에서 제공하는 AR RAY CAST로 바닥 플레인을 탐지해서 해당 레이케스트가 닿는 부
    분에 물체를 소환한다. 스크립트에서는 해당 레이케스트가 닿은 위치에 소환하고 물체는 카메라 방향을
    볼 수 있게 했다. 이미 물체가 소환된 경우에는 자리 이동만 할 수 있게 스크립트를 구성했다.

    private void placeObjectByTouch()
    {
    	if (Input.touchCount > 0 && hold == false
    		&& !IsPointerOverUIObject(Input.mousePosition))
    	{
    		if (EventSystem.current.IsPointerOverGameObject(
    			Input.GetTouch(0).fingerId))
    			return;
    		else
    		{
    			Touch touch = Input.GetTouch(0);
    			List<ARRaycastHit> hits = new
    				List<ARRaycastHit>();
    			if
    				(arRaycaster.Raycast(touch.position, hits,
    					TrackableType.Planes))
    			{
    				Pose hitPose = hits[0].pose;
    				if (!spawnObject)
    				{
    					spawnObject =
    						Instantiate(placeObject, hitPose.position,
    							hitPose.rotation);
    					scan_status.text = "고양이 소환";
    					Vector3 lookAtPosition =
    						spawnObject.transform.position;
    					lookAtPosition.x
    						= transform.position.x;
    					lookAtPosition.z
    						= transform.position.z;
    					transform.LookAt(lookAtPosition);
    					slider.SetActive(true);
    					catAnime.SetActive(true);
    				}
    				else
    				{
    					spawnObject.transform.position = hitPose.position;
    					spawnObject.transform.rotation = hitPose.rotation;
    					Vector3 lookAtPosition =
    						spawnObject.transform.position;
    					lookAtPosition.x
    						= transform.position.x;
    					lookAtPosition.z
    						= transform.position.z;
    					spawnObject.transform.LookAt(lookAtPosition);
    				} 터치한 곳에 고양이 오브젝트를
    			}//
    			소환, 이미 소환된 상태면 자리 이동만 함
    		}
    	}
    }

     

    2) 블루투스 기능
    증강현실을 탑재한 포켓몬고는 집 안이나 PC방에서 하던 기존 게임과는 달리 실외로 나가서 가상의 야
    생에 있는 포켓몬을 잡기 위해 실제 밖으로 나가야 하고 움직여야 , 한다는 특징이 있다. 이러한 설정에 영
    감받아 폰캣은 본인의 고양이를 친구의 고양이와 만나기 위해서 실제로 만나야 한다. 이를 위해 근거리
    통신인 블루투스를 사용한다.
    스마트폰의 블루투스는 유니티에서 직접 접근하지 못하고 안드로이드 기반 언어는 JAVA를 사용해야 되
    기 때문에 안드로이드 스튜디오에서 미리 제작된 Android 라이브러리를 통해 접근 할 수 있다. 표2에서
    는 라이브러리를 인스턴스로 받아와서 사용하는 스크립트다. 해당 스크립트로 토스트 메시지도 활용할
    수 있었다.

    public class AndroidPlugin : MonoBehaviour
    {
    	블루투스 // 기능은 안드로이드 네이티브 기
    		능으로서 안드로이드 스튜디오의
    		AAR 라이브러리를 불러와서 사용해야한
    		다.
    		private AndroidJavaObject UnityActivity;
    	private AndroidJavaObject UnityInstance;
    	void Start()
    	{
    		AndroidJavaClass ajc = new
    			AndroidJavaClass("com.unity3d.player.UnityPl
    				ayer");
    				UnityActivity =
    				ajc.GetStatic<AndroidJavaObject>("currentActi
    					vity");
    					AndroidJavaClass ajc2 = new
    					AndroidJavaClass("com.nsu.forunity.Myplugin")
    					;
    		UnityInstance =
    			ajc2.CallStatic<AndroidJavaObject>("instance")
    			;
    		UnityInstance.Call("setContext",
    			UnityActivity);
    	}
    	// 토스트 메시지
    	public void ShowToast(string msg, bool
    		isLong)
    	{
    		UnityActivity.Call("runOnUiThread",
    			new AndroidJavaRunnable(() = >
    		{
    			if (isLong == false)
    			{
    				UnityInstance.Call("ShowToast", msg, 0);
    			}
    			else
    			{
    				UnityInstance.Call("ShowToast", msg, 1);
    			}
    		}
    		));
    	}

     

    3) JSON 데이터를 이용한 상점 시스템
    상점은 따로 DB를 구축해 JSON으로 각 아이템 번호, 이름, 가격, 구매 유무를 관리했다. 향후 아이템
    을 추가하거나 제거할 때 편리하도록 구현했다.
    유니티에서 기본적으로 제공하는 JsonUtility.ToJson를 사용했다.

    제너릭 클래스(유사 탬플릿 클래스)를 사용해 List에 담아서 사용했다.

    DB 구축하기 위해서 txt파일을 파일 입출력으로 File.ReadAllText(filePath) 불러온다. 다음에는 JsonUtility로 Json화 한다.
    PC와 다르게 모바일 환경은 저장위치가 다르게 대응하기 때문에 Application.persistentDataPath앱
    의 저장되는 위치를 받아와서 사용하면 모바일 저장 위치를 확인 할 수 있다.

    [System.Serializable]
    public class Serialization<T>
    {
    	public Serialization(List<T> _target) = > target =
    		_target;
    	public List<T> target;
    }
    public class DataManager : MonoBehaviour
    {
    	public TextAsset ItemDatabase;
    	public List<Item> AllItemList;
    	string filePath;
    	private void Start()
    	{
    		// Load();
    		//txt 파일을 읽어옴. 엑셀 파일로 작성되었음
    		text 에셋으로 한줄 한줄 잘라냄
    			string[] line =
    			I t e m D a t a b a s e.t e x t.S u b s t r i n g(0,
    				ItemDatabase.text.Length - 1).Split('\n');
    		for (int i = 0; i < line.Length; i++)
    		{
    			line[i] = line[i].Trim(); //이거 추가
    			string[] row = line[i].Split('\t');
    			AllItemList.Add(new Item(row[0],
    				row[1], row[2], row[3]));
    		}
    		filePath = Application.persistentDataPath +
    			"/allitemdata.txt";
    	}
    	void Save()
    	{
    		string jdata = JsonUtility.ToJson(new
    			Serialization<Item>(AllItemList));
    		File.WriteAllText(filePath, jdata);
    	}
    	void Load()
    	{
    		//if(!File.Exists(filePath)) {ResetItemClick();
    		return;
    	}
    	string jdata = File.ReadAllText(filePath);
    	AllItemList = JsonUtility.FromJson<Serialization<Item
    		>>(jdata).target;
    }
    }

     

    4) 랜덤으로 작동하는 고양이의 행동
    유니티의 코루틴 함수를 사용해 Random.Range로 랜덤한 행동을 구현했다. 각 애니메이션은 랜덤한 시
    간으로 유지된다.
    실제로 살아있는 듯한 느낌을 주기 위해 모든 행동은 랜덤으로 결정하게 구현했다. 코루틴 함수를 사용한
    것은 Coroutine은 기존의 Thread를 좀 더 작은 단위로 쪼개어 사용할 수 있는 개념이기에 Thread에서
    복수의 이 실행될 경우 각 가 가지는 메모리 Coroutine Thread Stack 영역도 하나가 되어 메모리 절약이
    된다.

      IEnumerator Wander()
        {
            int rotTime =Random.Range(1,2);
            int rotateWait=Random.Range(1,3);
            int rotateLorR=Random.Range(0,7);
            int walkWait = Random.Range(1,3);
            int walkTime = Random.Range(4,10);
            int idletime = Random.Range(2,8);
            int sitdowntime = Random.Range(6,10);
    
            isWandering=true;
    
            yield return new WaitForSeconds(walkWait);
                 if (!audioSource.isPlaying)
                    Meow();
            isWalking = true;
            //WalkSound();
            yield return new WaitForSeconds(walkTime);
            isWalking = false;
            //애니메이션 끄기
            animator.SetBool("ismove", false);
            //
            yield return new WaitForSeconds(rotateWait);
    
            if(rotateLorR==1){
                isRotationRight=true;
                //WalkSound();
                yield return new WaitForSeconds(rotTime);
                 animator.SetBool("ismove", false);
                isRotationRight=false;
            }
            .
            .
            .생략

     

    5) Head up UI 스크립트
    블루투스 통신에서 고양이 정보가 고양이의 머리위에 계속해서 띄워져 있어야 한다. playerpref로 저장
    되어 있던 정보를 불러와서 해당 오브젝트의 위치를 업데이트 구문에서 받아와 띄워준다. playerpfref는
    보안에 취약하므로 간단한 정보만 저장했다. 예를 들어, 고양이의 위치는 매 10분마다 저장이 되어 다음
    에 앱을 실행해도 그 위치를 기억하는 로직도 구현했다. 그밖에 시간을 이용한 로직은 배고픔이나 심심함
    에도 적용이 되었다.

    그 밖에 UI위에 파티클 시스템을 적용하고, dotween 애니메이션 도구를 사용했다. 

    void Update()
    {
    	playerinfoUI.transform.position =
    		headUpPos.transform.position;
    	UITextput();
    }
    void UITextput()
    {
    	if (isMine == true)
    	{
    		string myname =
    			PlayerPrefs.GetString("name");
    		int	mylevel = PlayerPrefs.GetInt("statValue");
    		levelname_text.text = "LV. " +
    			Mathf.Floor(mylevel / 6f).ToString() + "\n" + myname;
    	}

     

    6) 현실 시간 반영한 배경(창문 밖)과 고양이의 상태
    DateTime 구조체를 사용해 현재의 시간을 불러와, 마지막으로 사료를 투여한 시간, 마지막으로 놀아준
    시간을 계산, 또한 현재의 시간에 따라 창 밖에 모습이 아침, 낮, 밤으로 다르게 표현하여 현재의 시간이
    어떤지 유저에게 알려주게 구현했다.

    public string nowTime()
    {
    	//스트링 값으로 현재 시간을 보내드립니다.
    	DateTime now;
    	now = DateTime.Now;
    	string st = now.ToString("yyyy-MM-dd
    		HH:mm:ss");
    		return st;
    }

     

    7) GOF 디자인 패턴의 활용
    하나의 오브젝트만 필요한 기능은 디자인 패턴인 싱글톤 디자인을 사용했다. 싱글톤 디자인은 고정된 메
    모리 영역을 얻으면서 한번의 new로 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있다.
    또한, 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들
    이 데이터를 공유하기 쉽다는 큰 장점이 있다. 하지만 모든 곳에서 접근이 가능하므로 싱글톤 객체의 변
    경 시점, 변경 주체, 호출 시점을 모두 알기가 어렵기 때문에 주의가 필요하다.

    고양이 상태도 상태패턴을 적용하여, 일반 제어문을 쓰지않고 관리했다면 좋았을텐데, 아쉬움이 남았다.

    public class BGM_START : MonoBehaviour
    {
    	private static BGM_START instance = null;
    	public static BGM_START Instance
    	{
    	get { return instance; }
    	}
    		void Start()
    	{
    		if (instance != null)
    		{
    			Destroy(this.gameObject);
    			return;
    		}
    		else
    		{
    			instance = this;
    		}
    		DontDestroyOnLoad(this.gameObject);
    	}
    }

    다시 구현한다면, 싱글톤을 만드는 로직을 제너릭으로 만들어서 여러 객체에 적용하고 싶다.

     

    8) 시네머신과 터치의 활용
    유니티에서는 클릭과 모바일의 터치가 객체가 따로 있다. Touch에는 다양한 객체의 상태가 있기 때문에
    클릭이 아닌 터치로 구현하는 것이 좋다. Touch Phase는 가장 최근의 프레임에 손가락이 취한 동작을
    나타내는데, Touch Phase의 열거형 값을 비교해 현재 터치 상태를 확인할 수 있다. 1. Began 2. Moved
    3. Stationary 4. Ended 5. Canceled가 그것이다. 위 표 8에서는 Ended, 화면 위를 벗어나 터치가 끝난
    상태를 받아오는 스크립트이다.
    시네머신은 유니티 엔진에서 제공하는 카메라 연출 패키지이다. 시네머신에도 애니메이터를 만들어서 4
    번 터치했을 경우 고양이를 집중적으로 렌더링하는 카메라를 활성화할 수 있다.

     

    void Update()
    {
    	//터치 량이 0보다 클때
    	if (Input.touchCount > 0)
    	{
    		Vector2 pos =
    			Input.GetTouch(0).position; // 터치한 위치
    		Vector3 theTouch = new Vector3(pos.x,
    			pos.y, 0.0f); // 변환 안하고 바로 Vector3로
    		Ray ray =
    			Camera.main.ScreenPointToRay(theTouch); // 터치
    		한 좌표 레이로 바꿈
    			RaycastHit hit; // 정보 저장할 구조체
    		만들고
    			if (Physics.Raycast(ray, out hit,
    				Mathf.Infinity))
    			{
    				// 캣이라는 태그를 가진 콜라이더에 닿
    				은 레이캐스트
    					if (hit.collider.CompareTag("cat"))
    					{
    						// 한 4번 터치 하면 이쪽 보게함
    						if (Input.GetTouch(0).phase ==
    							TouchPhase.Ended)
    						{
    							cattouchcount++;
    						}
    						if (cattouchcount > 4)
    						{
    							CamTrigger();
    							cattouchcount = 0;
    							....중략
    	
        //시네머신
    	public void CamTrigger()
    	{
    		if (!cam.GetBool("iszoom"))
    			cam.SetBool("iszoom", true);
    		else
    			cam.SetBool("iszoom", false);
    	}

     

    본 프로젝트는 안드로이드 기기로 고양이를 육성하고 인터렉션이 가능한 증강현실 속에서 누구나 쉽게
    플레이할 수 있는 안드로이드 기반 고양이 육성 게임으로, 블루투스 통신을 이용하여 근거리 유저 간의
    고양이와 같은 공간에서 놀이를 할 수 있고, 성별이 다르다면 교배를 통해 새끼를 낳을 수 있다. AR 기술
    을 통해 고양이를 실제 배경에 배치하여 볼 수 있다. 핸드폰 안에 있는 고양이와 놀아주기, 먹이 주기 활
    동으로 친밀감을 느끼게 할 수 있고, 스킨과 인테리어 변경을 통해 플레이어의 개성을 표현할 수 있다. 반
    려동물을 기르지 못해 대리만족을 느끼고 싶은 사람들에게 시뮬레이션 콘텐츠를 제공하였다. 

     

    https://github.com/jiwonchoidd/PhoneCat

     

    GitHub - jiwonchoidd/PhoneCat: Unity - AR, 블루투스, 상점 DB가 구현된 고양이 육성 시뮬레이션

    Unity - AR, 블루투스, 상점 DB가 구현된 고양이 육성 시뮬레이션. Contribute to jiwonchoidd/PhoneCat development by creating an account on GitHub.

    github.com

     

    COMMENT
     
    01
    03

    서버 코드

    더보기
    더보기
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <list>
    #include <string>
    #pragma comment	(lib, "ws2_32.lib")
    
    //서버
    struct User
    {
    	SOCKET		m_Sock;
    	SOCKADDR_IN m_Addr;
    	std::string m_csName;
    	short       m_iPort;
    	void set(SOCKET sock, SOCKADDR_IN addr)
    	{
    		m_Sock = sock;
    		m_Addr = addr;
    		m_csName = inet_ntoa(addr.sin_addr);
    		m_iPort = ntohs(addr.sin_port);
    	}
    };
    void main()
    {
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, 0);
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = htonl(INADDR_ANY);
    	int iRet = bind(ListenSock, (sockaddr*)&sa, sizeof(sa));
    	if (iRet == SOCKET_ERROR)  return;
    	iRet = listen(ListenSock, SOMAXCONN);
    	if (iRet == SOCKET_ERROR)  return;
    
    	SOCKADDR_IN clientAddr;
    	int iLen = sizeof(clientAddr);
    
    	std::cout
    		<< "서버 가동중......." << std::endl;
    
    	u_long on = 1;
    	ioctlsocket(ListenSock, FIONBIO, &on);
    
    	std::list<User> userlist;
    
    	while (1)
    	{
    		SOCKET clientSock = accept(ListenSock,
    			(sockaddr*)&clientAddr, &iLen);
    		if (clientSock == SOCKET_ERROR)
    		{
    			int iError = WSAGetLastError();
    			if (iError != WSAEWOULDBLOCK)
    			{
    				std::cout << "ErrorCode=" << iError << std::endl;
    				break;
    			}
    		}
    		else
    		{
    			User user;
    			user.set(clientSock, clientAddr);
    			userlist.push_back(user);
    
    			std::cout
    				<< "ip =" << inet_ntoa(clientAddr.sin_addr)
    				<< "port =" << ntohs(clientAddr.sin_port)
    				<< "  " << std::endl;
    			u_long on = 1;
    			ioctlsocket(clientSock, FIONBIO, &on);
    			std::cout << userlist.size() << " 명 접속중.." << std::endl;
    		}
    
    		if (userlist.size() > 0)
    		{
    			std::list<User>::iterator iter;
    			for (iter = userlist.begin(); iter != userlist.end(); )
    			{
    				User user = *iter;
    				char szRecvBuffer[256] = { 0, };
    				int iRecvByte = recv(user.m_Sock, szRecvBuffer, 256, 0);
    				if (iRecvByte == 0)
    				{
    					closesocket(user.m_Sock);
    					iter = userlist.erase(iter);
    					std::cout << user.m_csName << " 접속종료됨." << std::endl;
    					continue;
    				}
    				if (iRecvByte == SOCKET_ERROR)
    				{
    					int iError = WSAGetLastError();
    					if (iError != WSAEWOULDBLOCK)
    					{
    						iter = userlist.erase(iter);
    						std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    					}
    					else
    					{
    						iter++;
    					}
    				}
    				else
    				{
    					std::list<User>::iterator iterSend;
    					for (iterSend = userlist.begin();
    						iterSend != userlist.end(); )
    					{
    						User user = *iterSend;
    						std::cout << szRecvBuffer << "받음" << std::endl;
    						int iSendByte = send(user.m_Sock, szRecvBuffer, iRecvByte, 0);
    						std::cout << user.m_Sock << ":" << iSendByte << "보냄." << std::endl;
    						if (iSendByte == SOCKET_ERROR)
    						{
    							int iError = WSAGetLastError();
    							if (iError != WSAEWOULDBLOCK)
    							{
    								closesocket(user.m_Sock);
    								iterSend = userlist.erase(iterSend);
    								std::cout << user.m_csName << " 비정상 접속종료됨." << std::endl;
    							}
    						}
    						else
    						{
    							iterSend++;
    						}
    					}
    					if (iter != userlist.end())
    					{
    						iter++;
    					}
    				}
    			}
    		}
    	}
    	closesocket(ListenSock);
    	WSACleanup();
    }

     

    클라이언트 코드

    더보기
    더보기
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <iostream>
    #include <winsock2.h>
    #include <conio.h>
    #pragma comment	(lib, "ws2_32.lib")
    
    //클라이언트
    void main()
    {
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return;
    	}
    	SOCKET sock = socket(AF_INET,
    		SOCK_STREAM, 0); //SOCK_STREAM, SOCK_DGRAM
    			 //IPPROTO_TCP,IPPROTO_UDP
    	SOCKADDR_IN sa;
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family = AF_INET;
    	sa.sin_port = htons(10000);
    	sa.sin_addr.s_addr = inet_addr("ip");
    	int iRet = connect(
    		sock,
    		(sockaddr*)&sa,
    		sizeof(sa));
    	if (iRet == SOCKET_ERROR)
    	{
    		return;
    	}
    	std::cout << "접속성공!" << std::endl;
    
    	u_long on = 1;
    	ioctlsocket(sock, FIONBIO, &on);
    
    	char szBuffer[256] = { 0, };
    	int iEnd = 0;
    	while (1)
    	{
    		if (_kbhit() == 1)
    		{
    			int iValue = _getche();
    			if (iValue == '\r' && strlen(szBuffer) == 0)
    			{
    				break;
    			}
    			if (iValue == '\r')
    			{
    				int iSendByte = send(sock,
    					szBuffer,
    					iEnd,
    					0);
    
    				if (iSendByte == SOCKET_ERROR)
    				{
    					if (WSAGetLastError() != WSAEWOULDBLOCK)
    					{
    						std::cout << "비정상 서버종료됨.." << std::endl;
    					}
    				}
    				iEnd = 0;
    				ZeroMemory(szBuffer, sizeof(char) * 256);
    			}
    			else
    			{
    				szBuffer[iEnd++] = iValue;
    			}
    		}
    		else
    		{
    			char szRecvBuffer[256] = { 0, };
    			int iRecvByte = recv(sock, szRecvBuffer, 256, 0);
    
    			if (iRecvByte == 0)
    			{
    				std::cout << "서버종료됨.." << std::endl;
    				break;
    			}
    			if (iRecvByte == SOCKET_ERROR)
    			{
    				if (WSAGetLastError() != WSAEWOULDBLOCK)
    				{
    					std::cout << "비정상 서버종료됨.." << std::endl;
    					break;
    				}
    			}
    			else
    			{
    				std::cout << "\n" << szRecvBuffer;
    				ZeroMemory(szRecvBuffer, sizeof(char) * 256);
    			}
    		}
    	}
    	std::cout << "접속종료" << std::endl;
    	closesocket(sock);
    	WSACleanup();
    }
    COMMENT
     
    01
    03

    http://rastertek.com/dx11tut13.html

     

    Tutorial 13: Direct Input

    Tutorial 13: Direct Input This tutorial will cover using Direct Input in DirectX 11. The code in this tutorial will be based on the previous font tutorial code. Direct Input is the high speed method of interfacing with input devices that the DirectX API pr

    rastertek.com

     

    Window API 윈도우 메시지를 받아 키보드와 마우스 입력을 구현했었다.

    윈도우 입력보다 월등히 빠른 Direct Input은

    DirectX API가 제공하는 고속의 입력함수의 모음이다.

    게임과 같은 빠른 응답시간을 요구하는 프로그램이라면 Direct Input를 사용해야한다.

     

    KInput.h

     

    Input 클래스 헤더에는 버젼을 정의한다. 

    선언을 안했을때는 버전 8로 하겠다는 메시지를 띄운다.

    그리고 헤더파일을 포함시키고 두 라이브러리를 링크시킨다.

    #pragma comment(lib, "dinput8.lib") 
    #pragma comment(lib, "dxguid.lib")

     

    그리고 Direct Input에 대한 인터페이스를 만든다.

     

    LPDIRECTINPUT8		  m_pDI;
    LPDIRECTINPUTDEVICE8  m_pKeyDevice; // 키보드
    LPDIRECTINPUTDEVICE8  m_pMouseDevice;// 마우스

     

    싱글톤 클래스로 구현하기 때문에 포인터를 쓰지 않았다.

    아래는 상태값을 저장하는 변수들이다.

    이전 마우스 상태를 따로 만든것은 드래그를 구현하기 위함이다.

     

    DIMOUSESTATE		  m_DIMouseState;
    BYTE			m_MouseState[3];
    BYTE			m_BeforeMouseState[3];
    BYTE			m_KeyState[256];

     

    Direct Input의 인터페이스 초기화 함수

    더보기
    bool KInput::InitDirectInput()
    {
        HRESULT hr = S_OK;
        if (FAILED(hr = DirectInput8Create(g_hInstance,
            DIRECTINPUT_VERSION,
            IID_IDirectInput8,
            (void**)&m_pDI, NULL)))
        {
            return false;
        }
        if (FAILED(hr = m_pDI->CreateDevice(GUID_SysKeyboard,
            &m_pKeyDevice, NULL)))
        {
            return false;
        }
        // 장치별 반환 데이터 설정
        m_pKeyDevice->SetDataFormat(&c_dfDIKeyboard);
        // 장치별 독점 및 비독점 설정(협조레벨)
        if (FAILED(hr = m_pKeyDevice->SetCooperativeLevel(
            g_hWnd,
            DISCL_NONEXCLUSIVE |
            DISCL_FOREGROUND |
            DISCL_NOWINKEY)))
        {
            return false;
        }
    
        while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);
    
    
        if (FAILED(hr = m_pDI->CreateDevice(GUID_SysMouse,
            &m_pMouseDevice, NULL)))
        {
            return false;
        }
        m_pMouseDevice->SetDataFormat(&c_dfDIMouse);
    
        if (FAILED(hr = m_pMouseDevice->SetCooperativeLevel(
            g_hWnd,
            DISCL_NONEXCLUSIVE |
            DISCL_FOREGROUND)))
        {
            return true;
        }
        while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
        return true;
    }

     

    DXINPUT 인터페이스 객체 초기화

      HRESULT hr = S_OK;
        if (FAILED(hr = DirectInput8Create(g_hInstance,
            DIRECTINPUT_VERSION,
            IID_IDirectInput8,
            (void**)&m_pDI, NULL)))
        {
            return false;
        }

     

    위 코드처럼 LPDIRECTINPUT8 객체을 얻게되면 다른 입력장치 초기화 가능하다.

     

    if (FAILED(hr = m_pDI->CreateDevice(GUID_SysKeyboard,
            &m_pKeyDevice, NULL)))
        {
            return false;
        }
        // 장치별 반환 데이터 설정
        m_pKeyDevice->SetDataFormat(&c_dfDIKeyboard);

     

     협력레벨 협조레벨을 설정하는 것은 어떻게 사용되는지 결정하는 것이다.

    DLSCL_EXCLUSIVE 플래그는 다른 프로그램들과 공유하지 않는다.

    NONEXCLUSIVE로 했다. 캡쳐같은 입력을 하기 위해서다.

     

        // 장치별 독점 및 비독점 설정(협조레벨)
        if (FAILED(hr = m_pKeyDevice->SetCooperativeLevel(
            g_hWnd,
            DISCL_NONEXCLUSIVE |
            DISCL_FOREGROUND |
            DISCL_NOWINKEY)))
        {
            return false;
        }

     

    키보드 세팅이 끝나면 Acquire 함수로 키보드에 대한 접근을 취득함

     

     while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);

     

    마찬가지로 마우스 인터페이스 설정과 객체 초기화이다.

    위에 키보드와 같으니 설명은 생략한다.

     

    마우스 인터페이스 초기화 및 설정

     if (FAILED(hr = m_pDI->CreateDevice(GUID_SysMouse,
            &m_pMouseDevice, NULL)))
        {
            return false;
        }
        m_pMouseDevice->SetDataFormat(&c_dfDIMouse);
    
        if (FAILED(hr = m_pMouseDevice->SetCooperativeLevel(
            g_hWnd,
            DISCL_NONEXCLUSIVE |
            DISCL_FOREGROUND)))
        {
            return true;
        }
        while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
        return true;

     

    DXINPUT 객체 해제

    bool KInput::ShutDownDirectInput()
    {
        if (m_pMouseDevice)
        {
            m_pMouseDevice->Unacquire();
            m_pMouseDevice->Release();
            m_pMouseDevice = NULL;
        }
        if (m_pKeyDevice)
        {
            m_pKeyDevice->Unacquire();
            m_pKeyDevice->Release();
            m_pKeyDevice = NULL;
        }
        if (m_pDI)
        {
            m_pDI->Release();
            m_pDI = NULL;
        }
    }

     

    DirectInput은 해제전에 Unacquire()로 장치 해제를 해줘야한다.

    위 코드 Shutdown하는 함수이다. 

     

    DXINPUT 객체 Frame

    아래는 풀소스이다.

    더보기
    bool KInput::Frame()
    {
        // 화면좌표
        GetCursorPos(&m_ptPos);
        // 클라이언트 좌표
        ScreenToClient(g_hWnd, &m_ptPos);
    
        #pragma region 다이렉트 인풋 상태 읽어오기
        HRESULT hr;
        if (m_pMouseDevice == NULL || m_pKeyDevice == NULL) return false;
    
        if (FAILED(hr = m_pKeyDevice->GetDeviceState(256, m_KeyState)))
        {
            while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);
        }
        if (FAILED(hr = m_pMouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_DIMouseState)))
        {
            while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
        }
        #pragma endregion
    
        #pragma region  마우스 상태
        for (int iButton = 0; iButton < 3; iButton++)
        {
            m_BeforeMouseState[iButton] = m_DIMouseState.rgbButtons[iButton];
        }
        for (int iButton = 0; iButton < 3; iButton++)
        {
            if (m_BeforeMouseState[iButton] & 0x80)
            {
                if (m_MouseState[iButton] == KEY_FREE)
                    m_MouseState[iButton] = KEY_PUSH;
                else
                    m_MouseState[iButton] = KEY_HOLD;
            }
            else
            {
                if (m_MouseState[iButton] == KEY_PUSH ||
                    m_MouseState[iButton] == KEY_HOLD)
                    m_MouseState[iButton] = KEY_UP;
                else
                    m_MouseState[iButton] = KEY_FREE;
            }
        }
    
        ZeroMemory(&g_InputData, sizeof(INPUT_MAP));
    
        if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
        if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
        if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;
    
        if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
        if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
        if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;
    
        g_InputData.iMouseValue[0] = m_DIMouseState.lX;
        g_InputData.iMouseValue[1] = m_DIMouseState.lY;
        g_InputData.iMouseValue[2] = m_DIMouseState.lZ;
        #pragma endregion
      
        g_InputData.bWKey = GetKey(DIK_W);
        g_InputData.bAKey = GetKey(DIK_A);
        g_InputData.bSKey = GetKey(DIK_S);
        g_InputData.bDKey = GetKey(DIK_D);
    
        g_InputData.bLShift = GetKey(DIK_LSHIFT);
    
        g_InputData.bLeftKey = GetKey(DIK_LEFT);
        g_InputData.bRightKey = GetKey(DIK_RIGHT);
        g_InputData.bUpKey = GetKey(DIK_UP);
        g_InputData.bDownKey = GetKey(DIK_DOWN);
        g_InputData.bExit = GetKey(DIK_ESCAPE);
        g_InputData.bSpace = GetKey(DIK_SPACE);
        g_InputData.bExit = GetKey(DIK_ESCAPE);
    
        if (GetKey(DIK_F5) == KEY_HOLD) 	
        g_InputData.bChangeFillMode = true;
    
        return true;
    }

     

    제어권 재 취득 및 입력상태 저장

        HRESULT hr;
        if (m_pMouseDevice == NULL || m_pKeyDevice == NULL) return false;
    
        if (FAILED(hr = m_pKeyDevice->GetDeviceState(256, m_KeyState)))
        {
            while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);
        }
        if (FAILED(hr = m_pMouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_DIMouseState)))
        {
            while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
        }

     

    입력장치를 버퍼에 저장하기전에, 입력장치가 읽는데 실패할때가 있다.

    바로 포커스를 잃거나 취득불가상태인 경우이다. 이럴때는 제어권을 돌려받을때까지

    Acquire() 다시 취득하기 위해 반복문을 돌려하는 것이 좋다.  

    각 상태는 m_KeyState와 m_DIMouseState에 저장된다.

     

    마우스 인풋

        #pragma region  마우스 상태
        for (int iButton = 0; iButton < 3; iButton++)
        {
            m_BeforeMouseState[iButton] = m_DIMouseState.rgbButtons[iButton];
        }
        for (int iButton = 0; iButton < 3; iButton++)
        {
            if (m_BeforeMouseState[iButton] & 0x80)
            {
                if (m_MouseState[iButton] == KEY_FREE)
                    m_MouseState[iButton] = KEY_PUSH;
                else
                    m_MouseState[iButton] = KEY_HOLD;
            }
            else
            {
                if (m_MouseState[iButton] == KEY_PUSH ||
                    m_MouseState[iButton] == KEY_HOLD)
                    m_MouseState[iButton] = KEY_UP;
                else
                    m_MouseState[iButton] = KEY_FREE;
            }
        }

     

    마우스 상태는 m_DImouseSTATE.rgbButtons 배열로 받아온다. 

    0번 인덱스는 왼쪽마우스 1번 인덱스는 오른쪽 마우스 3번 인덱스는

    마우스 가운데 버튼을 의미한다.

     

     if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
        if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
        if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;
    
        if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
        if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
        if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;
    
        g_InputData.iMouseValue[0] = m_DIMouseState.lX;
        g_InputData.iMouseValue[1] = m_DIMouseState.lY;
        g_InputData.iMouseValue[2] = m_DIMouseState.lZ;

     

    g_InputData 구조체를 사용했다. 

     

    키보드 입력 상태 반환 함수

    BYTE KInput::GetKey(BYTE dwKey)
    {
        BYTE sKey;
        sKey = m_KeyState[dwKey];
        if (sKey & 0x80)
        {
            if (m_KeyStateOld[dwKey] == KEY_FREE)
                m_KeyStateOld[dwKey] = KEY_PUSH;
            else
                m_KeyStateOld[dwKey] = KEY_HOLD;
        }
        else
        {
            if (m_KeyStateOld[dwKey] == KEY_PUSH ||
                m_KeyStateOld[dwKey] == KEY_HOLD)
                m_KeyStateOld[dwKey] = KEY_UP;
            else
                m_KeyStateOld[dwKey] = KEY_FREE;
        }
        return m_KeyStateOld[dwKey];
    }

     

    비트연산 & 0x80로 키보드가 눌렸을때 상태를 정해주는 함수이다.

    사실 이 GetKey 함수로 바로 입력처리가 가능하지만

    g_Inputdata라는 구조체를 사용했다.

     

    g_InputData 전역 구조체

    struct INPUT_MAP
    {
    	bool bUpKey;
    	bool bDownKey;
    	bool bLeftKey;
    	bool bRightKey;
    
    	bool bWKey;
    	bool bSKey;
    	bool bAKey;
    	bool bDKey;
    	bool bQKey;
    	bool bEKey;
    	bool bZKey;
    	bool bCKey;
    
    	bool bLeftClick;
    	bool bRightClick;
    	bool bMiddleClick;
    
    	bool bLeftHold;
    	bool bRightHold;
    	bool bMiddleHold;
    
    	bool bExit;
    	bool bSpace; 
    
    	int  iMouseValue[3];
    
    	bool bFullScreen;
    	bool bChangeFillMode;
    	bool bChangePrimitive;
    	bool bChangeCullMode;
    	bool bChangeCameraType;
    	bool bDebugRender;
    };
    extern INPUT_MAP g_InputData;

     

    전역 구조체로 마우스의 상태와

    지정한 키 상태를 전역으로 저장한다. 

    구조체를 사용한 이유는 실제로 사용할때 간결해지고

    원하는 키입력만 조작하게 하기 위함이다.

     

    유니티나 언리얼에도 설정에서 인풋매니져에서 

    설정하듯이 등록해서 써야하듯이.

     

    만약 불리안 값의 입력 구조체 없이 한다면 이런 코드가 될것이다.

     

        if (g_Input.GetKey(DIK_W) >= KEY_PUSH)
        {
            m_vCameraPos = m_vCameraPos + m_vLook * m_fSpeed * g_fSecPerFrame;
        }
        if (g_Input.GetKey(DIK_S) >= KEY_HOLD)
        {
            m_vCameraPos = m_vCameraPos + m_vLook * -m_fSpeed * g_fSecPerFrame;
        }
        if (g_Input.GetKey(DIK_D) >= KEY_PUSH)
        {
            m_vCameraPos = m_vCameraPos + m_vSide * m_fSpeed * g_fSecPerFrame;
        }
        if (g_Input.GetKey(DIK_A) >= KEY_HOLD)
        {
            m_vCameraPos = m_vCameraPos + m_vSide * -m_fSpeed * g_fSecPerFrame;
        }

     

    하지만 입력 구조체를 사용한다면 간결해진다.

    그리고 만약에 WASD말고 방향키로 여러 입력을 받는 이동키의 경우

    이전 구조체를 안쓰는 버젼으로 하면 코드가 엄청 지저분해질것이다.

     

        if (g_InputData.bWKey)
        {
            m_vCameraPos = m_vCameraPos + m_vLook * m_fSpeed * g_fSecPerFrame;
        }
        if (g_InputData.bSKey)
        {
            m_vCameraPos = m_vCameraPos + m_vLook * -m_fSpeed * g_fSecPerFrame;
        }
        if (g_InputData.bAKey)
        {
            m_vCameraPos = m_vCameraPos + m_vSide * m_fSpeed * g_fSecPerFrame;
            m_vCameraTarget.x += m_fSpeed * g_fSecPerFrame;
        }
        if (g_InputData.bDKey)
        {
            m_vCameraPos = m_vCameraPos + m_vSide * -m_fSpeed * g_fSecPerFrame;
            m_vCameraTarget.x -= m_fSpeed * g_fSecPerFrame;
        }

     

    ZeroMemory(&g_InputData, sizeof(INPUT_MAP));
    
        if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
        if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
        if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;
    
        if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
        if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
        if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;
    
        g_InputData.iMouseValue[0] = m_DIMouseState.lX;
        g_InputData.iMouseValue[1] = m_DIMouseState.lY;
        g_InputData.iMouseValue[2] = m_DIMouseState.lZ;
        #pragma endregion
      
        g_InputData.bWKey = GetKey(DIK_W);
        g_InputData.bAKey = GetKey(DIK_A);
        g_InputData.bSKey = GetKey(DIK_S);
        g_InputData.bDKey = GetKey(DIK_D);
    
        g_InputData.bLShift = GetKey(DIK_LSHIFT);
    
        g_InputData.bLeftKey = GetKey(DIK_LEFT);
        g_InputData.bRightKey = GetKey(DIK_RIGHT);
        g_InputData.bUpKey = GetKey(DIK_UP);
        g_InputData.bDownKey = GetKey(DIK_DOWN);
        g_InputData.bExit = GetKey(DIK_ESCAPE);
        g_InputData.bSpace = GetKey(DIK_SPACE);
        g_InputData.bExit = GetKey(DIK_ESCAPE);
    
        if (GetKey(DIK_F5) == KEY_HOLD) 	
        g_InputData.bChangeFillMode = true;
    
        return true;
    }

     

    Frame 함수에 입력구조체를 사용해서

    매 프레임 입력받은 것을 처리해주고 전역 구조체를 다른 클래스에서

    접근해서 불리안 값인 구조체를 값을 판별해 입력의 유무를 구분한다.

     

    결과 

    COMMENT
     
    12
    31

       i. TCP UDP 장단점

     

    네트워크 프로그램을 만들 때는 먼저 사용할 소켓 타입을 결정해야한다. 소켓에는 두가지 정보를 입력해야 하는데, 주소 체계와 소켓의 타입이다. 주소 체계에는 다음과 같은 종류가 있다.

     

    Name(이름) Address Family(주소체계)
    AF_INET IPv4 인터넷 주소 체계
    AF_INET6 IPv6 인터넷 주소 체계
    AF_LOCAL 로컬 통신 UNIX 주소 체계

     

    소켓 타입에는 TCPUDP로 나뉘게 된다. TCPTransmission Control Protocol의 약자이고, UDPUser Datagram Protocol의 약자이자, 두 프로토콜 모두 전송계층에 속한다.

    전송 계층이란, IP에 의해 전달되는 패킷의 오류를 검사하고 재전송 요구 등의 제어를 담당한다. 하지만 서로 다른 특징을 가지고 있다.

     

    구분 TCP UDP
    신뢰성 신뢰성을 위해 ACK, Checksum 등 사용 신뢰성이 없음
    연결성 연결 지향성
    Connection 을 맺고 통신
    비 연결성
    재전송 재전송 요청함
    (오류 및 패킷 손실 검출시)
    재 전송 없음
    특징 Flow Control을 위해 Windowing 사용
    속도는 다소 느려도 신뢰성을 제공
    신뢰성은 보장 하지 않지만 고속 데이터 전송
    실시간 전송에 적합
    용도 신뢰성이 필요한 통신 총 패킷수가 적은 통신
    동영상 및 음성 등 멀티미디어 통신

     

    TCP는 연결 지향성이어서 Connection을 맺고 통신을 한다. 연결이 되어 있어서 전송이 되었는지 확인이 가능하다. , 신뢰성이 보장된다. UDP는 일방적으로 한 쪽에서 보내기만 한다.

    그런 특성으로 UDP10byte 데이터를 보내고 싶으면 한 번에 10byte를 모두 전송되거나 10byte 전부 실패한다. 반면에 TCP 10 byte가 도착될 수도 5byte 2번 도착할 수도 1byte 10번 도착될 수도 있다.

    TCP는 신뢰성이 있는 전송이 중요할 때에 사용하는 프로토콜이며, 전송 순서와 신뢰성 있는 데이터를 전송하는 장점이 있지만, UDP보다 비교적 느리다 라는 단점이 있다. UDPTCP보다 속도가 빠르고 네트워크 부하가 적다는 장점이 있지만, 신뢰성 있는 데이터 전송을 보장하지 않는다는 단점이 있다.

    , 신뢰성이 요구되는 프로그램에서는 TCP를 사용하고 간단한 데이터를 고속으로 전송하고자 하면 UDP를 사용한다.

    프로그래밍적으로 접근하자면 UDP는 연결이 안되어 있기 때문에, 프로그램적으로 신뢰성을 보장해줘야 한다. 또한 한 번에 받기 때문에, 나중에 받은 것이 먼저 보냈던 걸 수도 있다. 어떤 게 먼저 보낸 건지 시간 설정을 고려해야 할 것이다.

    TCP는 패킷이 쪼개져서 연속적으로 들어오기 때문에 10Byte를 보냈으면 10Byte가 다 전송이 되었다는 걸 인지하고 포장해서 데이터로 써야한다. 패킷 상단에 몇 바이트를 보내는 패킷인지 명시하는 것을 고려해야한다.


    Socket

    소켓은 1982년 BSD Unix 4.1에서 처음 소개되었다.

    현재 널리 사용되는 것은 1986년 BSD Unix 4.3에서 개정된것.

     

    소켓은 소프트웨어로 작성된 통신 접속점으로

    응용프로그램에서 TCP/IP를 이용하는 창구 역활을 한다.

     

    윈속(WinSock)

    마이크로소프트 윈도즈와 소켓의 합성어 윈도우 소켓의 약자

    BSD 계열 유닉스 소켓을 참고로 하여 설계하였다.

    기능은 소켓과 같은데 윈도우 최적화에 개선을 한 것이다. 

     

    원속 환경설정

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include "Sample.h"
    #include <iostream>
    #include <winsock2.h> // 헤더
    #pragma comment (lib, "ws2_32.lib") // 라이브러리 추가 Ws2_32.dll 로드함

     

    원속 초기화

    //윈속 초기화
    int WSAStartup(WORD wVersionRequested, LPWSADATA LpWSAData);
    //윈속 종료 함수
    int WSACleanup(void)

    모든 윈속 프로그램에서 소켓 API 사용전에 윈속 초기화 함수를 먼저 호출 해야함.

    윈속 2.2 버젼은 0x0202 또는 MAKEWORD(2,2)를 사용하면됨

     

    소켓

    SOCKET socket(int af, //주소체계 지정
    			int type, // 소켓타입 지정
                    int protocol); // 사용할 프로토콜 지정

    반환값 : SOCKET(32비트 정수)

    성공하면 새로운 소켓을 하고 실패하면 INVALID_SOCKET를 리턴함

    #define SOCK_STREAM     1               /* stream socket */
    #define SOCK_DGRAM      2               /* datagram socket */

    stream은 TCP, dgram은 UDP 소켓으로

    protocol를 0으로 두면 알아서 프로토콜이 지정이 된다.

     

    아래는 예시로 클라이언트 콘솔 

    TCP/IP로 호스트 port에 연결하는 코드이다.

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include "Sample.h"
    #include <iostream>
    #include <winsock2.h>
    #pragma comment (lib, "ws2_32.lib")
    
    //클라이언트 
    int main()
    {
    	WSADATA wsa;
    	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
    	{
    		return 0;
    	}
    	//소캣
    	SOCKET sock = socket(AF_INET,SOCK_STREAM,0);//IP, STREAM - TCP // DGRAM- UDP, 프로토콜 안넣어도 됨
    	
    	//sockaddr addr;
    	//addr.sa_family = AF_INET; //IP 프로토콜 쓴다.
    	//addr.sa_data[14]; // ip. port
    	SOCKADDR_IN sa; //공용체써서 편한 대문자 버젼
    	ZeroMemory(&sa, sizeof(sa));
    	sa.sin_family= AF_INET;
    	sa.sin_port=htons(10000);
    	sa.sin_addr.s_addr= inet_addr("192.118.0.0");
    	int iRet = connect(sock,(sockaddr*)&sa,sizeof(sa)); //연결
    
    
    	char szBuffer[256] = "Hello World";
    	char szRecvBuffer[256] = { 0, };
    
    	int sendByte = send(sock, szBuffer, sizeof(szBuffer), 0);
    
    	recv(sock, szRecvBuffer, 256, 0);
    
    	std::cout << szBuffer<<"\n";
    
    	WSACleanup();
    }
    COMMENT
     
    12
    30

    IMGUI란?

    Immediate Mode GUI라는 뜻으로
    logger, profiler, debugger 게임 편집 등 C++에서
    사용자 인터페이스를 빠르고 쉽게 만들 수 있는
    라이브러리이다.

    주로 게임 개발에 사용되는데, 헤더 파일과 소스파일만 넣으면 바로 사용이 가능하다. 심지어 자체적으로 파이프라인을 타서 별도로 해줄 것이 거의 없다.

    https://github.com/ocornut/imgui

     

    GitHub - ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

    Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies - GitHub - ocornut/imgui: Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

    github.com

    깃허브에서 다운로드 받을 수 있다.. 다이렉트x11 기준으로 포스팅 하겠다.

    필요한 헤더파일과 소스파일


    모두 불러왔다면, 고려해야할 것들은
    Imgui, Imgui_dx11, imgui_win 총 3가지
    객체를 다뤄야한다는 것이다.

    그전에 imgui_impl_dx11 헤더와 win32에 기존 프로젝트의
    KWindow 클래스(윈도우 생성 클래스)와 Directx11클래스
    (다이렉트 디바이스 생성 클래스 )의 헤더를 포함시켜야한다.
    개인이 생성한 Device나 Hwnd를 연동하기 위해서이다.

    헤더 추가
    헤더 추가

    다음으로는 IMGUI를 관리해줄 IMGUI 매니져 클래스를 만든다.
    효율적으로 관리하기위해 클래스를 별도로 만들었다.

    ImGuiManager.cpp

    더보기
    #include "ImGuiManager.h"
    #include "ImGui/imgui.h"
    #include "ImGui/imgui_impl_dx11.h"
    #include "ImGui/imgui_impl_win32.h"
    void ImGuiManager::Frame()
    {
        if (m_bImguiEnable) {
            ImGui_ImplDX11_NewFrame();
            ImGui_ImplWin32_NewFrame();
            ImGui::NewFrame();
        }
    }
    void ImGuiManager::Render()
    {
        if (m_bImguiEnable) {
            ImGui::Render();
            ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
        }
    }
    void ImGuiManager::OnOffImgui() { m_bImguiEnable = !m_bImguiEnable; }
    bool ImGuiManager::isImguiEnable() { return m_bImguiEnable; }
    ImGuiManager::ImGuiManager()
    {
        IMGUI_CHECKVERSION();
        ImGui::CreateContext();
        ImGui::StyleColorsDark();
    }
    ImGuiManager::~ImGuiManager()
    {
    ImGui::DestroyContext();
    }


    생성자와 해제자. Imgui를 생성하고 해제한다.

    ImGuiManager::ImGuiManager()
    {
        IMGUI_CHECKVERSION();
        ImGui::CreateContext();
        ImGui::StyleColorsDark();
    }
    ImGuiManager::~ImGuiManager() { ImGui::DestroyContext(); }


    이제 생성을 했으면 초기화를 해야한다.
    Window는 ShowWindow를 호출하고 나서
    초기화하고 DXDevice는 뷰포트를 세팅하고 나서
    초기화하면 된다.(setviewport)

    IMGUI_Win 초기화, 소멸 작업

    헤더 추가
    ShowWindow 호출 후에 초기화


    wndhandler가 만들어진 후에 imgui_win를 생성한다.
    그리고 클릭이나 윈도우 메세지를 imgui도 받기위해
    메세지 프로시저에 imgui_win의 wndproc를 넣어준다.
    반환될게 없기 때문에 그냥 true로 했다.


    소멸자에서 imgui_win를 해제해주는 함수를 쓴다.

    소멸

     

    Directx IMGUI 초기화 작업

    RSSetviewports() 함수 호출 후에 초기화를 해준다.


    마찬가지로 위에 imgui_win처럼 생성을 하고

    해제자에서 ImGui_ImplDX11_Shutdown() 함수를 호출해 릴리즈해주는

    작업을 똑같이 해준다. 그러면 세팅은 끝났다.

    Game Render 작업

     

    더보기
    #include "ImGuiManager.h"
    #include "ImGui/imgui.h"
    #include "ImGui/imgui_impl_dx11.h"
    #include "ImGui/imgui_impl_win32.h"
    void ImGuiManager::Frame()
    {
        if (m_bImguiEnable) {
            ImGui_ImplDX11_NewFrame();
            ImGui_ImplWin32_NewFrame();
            ImGui::NewFrame();
        }
    }
    void ImGuiManager::Render()
    {
        if (m_bImguiEnable) {
            ImGui::Render();
            ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
        }
    }
    void ImGuiManager::OnOffImgui() { m_bImguiEnable = !m_bImguiEnable; }
    bool ImGuiManager::isImguiEnable() { return m_bImguiEnable; }
    ImGuiManager::ImGuiManager()
    {
        IMGUI_CHECKVERSION();
        ImGui::CreateContext();
        ImGui::StyleColorsDark();
    }
    ImGuiManager::~ImGuiManager() { ImGui::DestroyContext(); }


    위에 풀소스를 보면 Frame에서는 bool값으로
    켜고 끄고 가능하게 제작하였고,
    Render에서는 렌더를 담당하였다.


    IMGUI매니져 객체를 만들어 마지막에 렌더를 했다. 이유는
    IMGUI는 각자의 파이프라인을 타기때문에 Zbuffer가 없다.
    별도로 뿌리기때문에 오브젝트를 뿌리기전에 Imgui를 렌더하면
    오브젝트에 가려지게 된다. 아래처럼 된다.


    그럼 간단하게 IMGUI를 사용해서

    게임 시간을 조절하고 Frame를 출력해보자.

    static char buffer[1024];
    if (ImGui::Begin("Simulation Speed")) {
        ImGui::InputText("Input text", buffer, sizeof(buffer));
        ImGui::SliderFloat("Speed Factor", &m_Speed, 0.0f, 4.0f);
        ImGui::Text("Average %.3f ms/Frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
    }
    ImGui::End();


    별도로 Frame를 계산하지 않아도, Imgui의 별도의 프레임 연산이 있다.
    ImGui::GetIO()에 포함되어 있다.

    ImGui :: Begin() - 함수는 시작이자, 창의 제목을 담당한다.
    ImGui :: SliderFloat() - 슬라이더로 값을 조정한다.
    ImGui :: Text() - printf문과 유사하다. 글자를 출력하다.
    ImGui :: End() - 창 레이아웃 종료

    결과

    IMGUI 슬라이더를 이용해 속도 조절&amp;amp;amp;nbsp;


    이외에도 RGB 선택기나, 저장기능, 그래프 등 여러 기능을 포함하고 있다.
    자세한건 공식 문서 참조해야겠다.
    그나저나 난 아엠지유아이라고 불렀는데 어떤 사람은 임구이라 부르더라.
    임구이라니..

    source :&amp;amp;amp;amp;nbsp;https://github.com/ocornut/imgui

    COMMENT
     
    12
    28

    한 함수로 3D박스 그리기

    더보기
    bool DrawTestBox(float angle, float x, float y, float z)
    	{
    		namespace wrl = Microsoft::WRL;
    		HRESULT hr = S_OK;
    		struct Vertex
    		{
    			struct {
    				float x;
    				float y;
    				float z;
    			}pos;
    		};
    		const Vertex vertices[] =
    		{
    			{-1.0f,-1.0f,-1.0f},
    			{ 1.0f,-1.0f,-1.0f},
    			{-1.0f,1.0f,-1.0f},
    			{ 1.0f,1.0f,-1.0f},
    			{-1.0f,-1.0f,1.0f},
    			{ 1.0f,-1.0f,1.0f},
    			{-1.0f,1.0f,1.0f},
    			{ 1.0f,1.0f,1.0f},
    		};
    		wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
    		D3D11_BUFFER_DESC bd;
    		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    		bd.Usage = D3D11_USAGE_DEFAULT;
    		bd.CPUAccessFlags = 0u;
    		bd.MiscFlags = 0u;
    		bd.ByteWidth = sizeof(vertices);
    		bd.StructureByteStride = sizeof(Vertex);
    
    		D3D11_SUBRESOURCE_DATA sd;
    		ZeroMemory(&sd,sizeof(D3D11_SUBRESOURCE_DATA));
    		sd.pSysMem = vertices;
    		hr=g_pd3dDevice->CreateBuffer(&bd, &sd, pVertexBuffer.GetAddressOf());
    		if (FAILED(hr)) return false;
    		const UINT stride = sizeof(Vertex);
    		const UINT offset = 0u;
    		//배열이 들어갈수있다.
    		m_pImmediateContext->IASetVertexBuffers(0u, 1u, pVertexBuffer.GetAddressOf(), &stride, &offset);
    
    		const unsigned short indices[] =
    		{
    			0,2,1, 2,3,1,
    			1,3,5, 3,7,5,
    			2,6,3, 3,6,7,
    			4,5,7, 4,7,6,
    			0,4,2, 2,4,6,
    			0,1,4, 1,5,4,
    		};
    		wrl::ComPtr<ID3D11Buffer>pIndexBuffer;
    		D3D11_BUFFER_DESC ibd = {};
    		ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    		ibd.Usage = D3D11_USAGE_DEFAULT;
    		ibd.CPUAccessFlags = 0u;
    		ibd.MiscFlags = 0u;
    		ibd.ByteWidth = sizeof(indices);
    		ibd.StructureByteStride = sizeof(unsigned short);
    		D3D11_SUBRESOURCE_DATA isd;
    		isd.pSysMem = indices;
    		hr = g_pd3dDevice->CreateBuffer(&ibd, &isd, pIndexBuffer.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		m_pImmediateContext->IASetIndexBuffer(pIndexBuffer.Get(),DXGI_FORMAT_R16_UINT,0);  
    
    		//상수버퍼
    		struct ConstantBuffer
    		{
    			dx::XMMATRIX transform;
    		};
    		const ConstantBuffer cb =
    		{
    			{
    				dx::XMMatrixTranspose(
    					dx::XMMatrixRotationZ(angle) *
    					dx::XMMatrixRotationY(angle) *
    					dx::XMMatrixTranslation(x,y,z+2.0f) *
    					dx::XMMatrixPerspectiveLH(1.0f,3.0f / 4.0f,0.1f,1000.0f)
    				)
    			}
    		};
    		wrl::ComPtr<ID3D11Buffer>pConstantBuffer;
    		D3D11_BUFFER_DESC cbd = {};
    		cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    		cbd.Usage = D3D11_USAGE_DYNAMIC;
    		cbd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    		cbd.MiscFlags = 0u;
    		cbd.ByteWidth = sizeof(cb);
    		cbd.StructureByteStride = 0;
    		D3D11_SUBRESOURCE_DATA csd;
    		csd.pSysMem = &cb;
    		hr = g_pd3dDevice->CreateBuffer(&cbd, &csd, pConstantBuffer.GetAddressOf());
    		if (FAILED(hr)) return false;
    		m_pImmediateContext->VSSetConstantBuffers(0,1,pConstantBuffer.GetAddressOf());
    
    		struct ConstantBuffer2
    		{
    			struct
    			{
    				float r;
    				float g;
    				float b;
    				float a;
    			}face_colors[6];
    		};
    		const ConstantBuffer2 cb2 =
    		{
    			{
    				{0.5f,1.0f,0.0f},
    				{0.0f,0.5f,1.0f},
    				{1.0f,0.0f,0.5f},
    				{0.5f,0.5f,1.0f},
    				{0.5f,1.0f,0.5f},
    				{1.0f,0.5f,0.5f},
    			}
    		};
    		wrl::ComPtr<ID3D11Buffer>pConstantBuffer2;
    		D3D11_BUFFER_DESC cbd2 = {};
    		cbd2.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    		cbd2.Usage = D3D11_USAGE_DYNAMIC;
    		cbd2.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
    		cbd2.MiscFlags = 0u;
    		cbd2.ByteWidth = sizeof(cb2);
    		cbd2.StructureByteStride = 0;
    		D3D11_SUBRESOURCE_DATA csd2;
    		csd2.pSysMem = &cb2;
    		hr = g_pd3dDevice->CreateBuffer(&cbd2, &csd2, pConstantBuffer2.GetAddressOf());
    		if (FAILED(hr)) return false;
    		m_pImmediateContext->PSSetConstantBuffers(0, 1, pConstantBuffer2.GetAddressOf());
    
    		wrl::ComPtr<ID3DBlob> pBlob;
    
    		//1. 픽셀 셰이더 생성
    		wrl::ComPtr<ID3D11PixelShader> pPixelShader;
    		hr=D3DReadFileToBlob(L"PixelShader.cso", pBlob.GetAddressOf());
    		if (FAILED(hr)) return false;
    		hr = g_pd3dDevice->CreatePixelShader(pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(), nullptr, pPixelShader.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		//2. 바인드 픽셀 셰이더
    		m_pImmediateContext->PSSetShader(pPixelShader.Get(), nullptr, 0u);
    
    		//3. 버텍스 셰이더 생성
    		wrl::ComPtr<ID3D11VertexShader> pVertexShader;
    		hr=D3DReadFileToBlob(L"VertexShader.cso", pBlob.GetAddressOf());
    		if (FAILED(hr)) return false;
    		hr = g_pd3dDevice->CreateVertexShader(pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(), nullptr, pVertexShader.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		//4. 바인드 버텍스 셰이더
    		m_pImmediateContext->VSSetShader(pVertexShader.Get(), nullptr, 0u);
    
    		//5. Input 버텍스 레이아웃 (2D 위치만)
    		wrl::ComPtr<ID3D11InputLayout> pInputLayout;
    
    		const D3D11_INPUT_ELEMENT_DESC layout[] =
    		{
    			{"POSITION",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA, 0},
    		};
    		hr = g_pd3dDevice->CreateInputLayout(layout, _countof(layout),
    			pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(),
    			pInputLayout.GetAddressOf()
    		);
    		if (FAILED(hr)) return false;
    
    		//6. 바인드 버텍스 레이아웃
    		m_pImmediateContext->IASetInputLayout(pInputLayout.Get());
    
    		m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    
    		m_pImmediateContext->DrawIndexed((UINT)(std::size(indices)),0u,0u);
    		
    		return true;
    	};

     

     

    위 함수는 이렉트의 파이프라인을 타고
    "박스"를 그리는 함수이다.

    렌더링 함수에서 4개의 박스를 그리도록 해보자
    타이머 뒤에 인자는 x y z로 (왼손좌표계)
    y축으로 올라갈수록 z축으로 더 멀어지고 있는 모습이다.

    가장 위에 있는 박스가 가장 멀리 있다고 결과를 유추해 볼 수 있다.

     

    DepthStencilView 적용전

    깊이 버퍼 적용전


    가장 위에 있는 박스가 가장 앞에 있는 모습이다.
    이렇게 되는 이유는 함수를 실행한 순서대로 그려지고 있기 때문이다.

    도화지에 함수를 통해 그림을 그린다고 생각하면 된다. 아크릴 물감처럼!
    정점들의 깊이를 저장해서 최종 Draw가 되는 것을 결정하는 텍스쳐가 필요하다.

    https://docs.microsoft.com/ko-kr/windows/uwp/graphics-concepts/depth-and-stencil-buffers

     

    깊이 및 스텐실 버퍼 - UWP applications

    깊이 버퍼는 보기에서 숨기는 대신 렌더링하는 다각형의 영역을 제어하기 위한 깊이 정보를 저장합니다.

    docs.microsoft.com

     

    Depth & Stencil

    depth와 stencil 둘다 마스킹하는 역활을 한다.
    미묘한 차이가 있다.

    Depth는 깊이 버퍼로 깊이 정보를 담는다. 가릴수는 있어도 렌더를 안하게 할 수는 없다.
    Stencil은 픽셀을 그릴지 말지, 이미지의 픽셀을 특수효과로 처리하는데에 쓰인다.

    그래픽스 파이프라인에서 OM단계에 속한다. 제일 마지막 단계이다.

    Init()
    1. 텍스쳐 생성 : 깊이, 스텐실 값을 저장하는 버퍼용
    2. 뎁스 스텐실 뷰 생성 : 텍스쳐를 담아 뎁스 스텐실 뷰를 만든다.
    3. 뎁스 스텐실 스테이트 생성 : 속성 설정으로 가려지면 렌더 안할지, 가려질지 등 구조체 설정 (DepthFunc의 Comparison)

    PreRender()
    4. 뎁스스텐실 뷰 클리어 : 렌더타겟뷰를 클리어 하듯이 뎁스스텐실도 매번 클리어해줘야함 (스왑체인)
    5. OMSetRenderTarget에 적용 : OMSetRenderTargets() 함수에는 스텐실뷰를 넣는 인자가 있다.
    6. 뎁스스텐실 스테이트 적용 : 만들었던 상태 구조체를 pContext에 적용

     

    텍스쳐 생성, DepthStencilView 생성

    더보기
    HRESULT KDevice::SetDepthStencilView()
    {
    	// 1)텍스처 생성 : 깊이,스텐실 값을 저장하는 버퍼용
    	HRESULT hr = S_OK;
    	DXGI_SWAP_CHAIN_DESC SDesc;
    	m_pSwapChain->GetDesc(&SDesc);
    	ID3D11Texture2D* pDSTexture = nullptr;
    	D3D11_TEXTURE2D_DESC DescDepth;
    	DescDepth.Width = SDesc.BufferDesc.Width;
    	DescDepth.Height = SDesc.BufferDesc.Height;
    	DescDepth.MipLevels = 1;
    	DescDepth.ArraySize = 1;
    	//RGB는 텍스쳐 리소스 D24는 뎁스
    	DescDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    	DescDepth.SampleDesc.Count = 1;
    	DescDepth.SampleDesc.Quality = 0;
    	DescDepth.Usage = D3D11_USAGE_DEFAULT;
    	DescDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    	DescDepth.CPUAccessFlags = 0;
    	DescDepth.MiscFlags = 0;
    	hr = g_pd3dDevice->CreateTexture2D(&DescDepth, nullptr, &pDSTexture);
    	if (FAILED(hr))
    	{
    		return hr;
    	}
    	if (pDSTexture == nullptr)
    	{
    		return E_FAIL;
    	}
    	//뎁스 스텐실 뷰 만듬
    	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
    	ZeroMemory(&descDSV, sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
    	descDSV.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    	descDSV.Texture2D.MipSlice = 0;
    	hr = g_pd3dDevice->CreateDepthStencilView(pDSTexture, &descDSV,
    		&m_DepthStencilView);
    	if (FAILED(hr))
    	{
    		return hr;
    	}
    	return hr;
    }


    텍스쳐를 만들고 BindFlags를 뎁스스텐실용으로 한다.
    Format도 보통 RGB인데 D인 이유는 뎁스용이기 때문이다.
    SampleDesc.Count = 1;
    SampleDesc.Quality = 0;
    이 위에 있는 코드는 안티엘리어싱 때 필요한 코드로

    swapchain 생성

    스왑체인때도 봤던 똑같은 구조체 변수이다.
    둘은 같아야한다. 왜냐하면 둘다 같은 뷰이기 때문이다.

     


    텍스쳐를 만들었으면 뎁스스텐실뷰를 만들 수 있다.
    하지만 스테이트(상태)까지 해야 우리가 원하는 화면을 얻을 수 있다.

     

    DepthStencilView State 생성

    더보기
    HRESULT KState::CreateDepthStenState()
    {
        HRESULT hr = S_OK;
        D3D11_DEPTH_STENCIL_DESC dsd;
        ZeroMemory(&dsd, sizeof(D3D11_DEPTH_STENCIL_DESC));
        dsd.DepthEnable = TRUE;
        dsd.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
        //1.0, 0.6과 0.5를 비교해서 크면 실패
        //제일 앞에 있는 면이 뿌려지면 뒤에있는애들은 렌더 안됨
        dsd.DepthFunc = D3D11_COMPARISON_LESS;
        dsd.StencilEnable = TRUE;
        dsd.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK;
        dsd.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK;
        dsd.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        dsd.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
        dsd.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        dsd.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
        dsd.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        dsd.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
        dsd.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        dsd.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
        hr = g_pd3dDevice->CreateDepthStencilState(&dsd, &g_pDSState);
        if (FAILED(hr))
        {
            return hr;
        }
        //깊이 스텐실 제일 마지막 결과 기반으로 렌더하는 것이기에 OM
        return hr;
    }


    많은 속성이 있지만, 여기서 제일 중요한건
    dsd.DepthEnable = TRUE;
    dsd.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    dsd.DepthFunc = D3D11_COMPARISON_LESS;

    Comparison은 less로 되어있는데
    뎁스값이 0~1인데 해당 객체의 뎁스값이~ 작으면 마스킹된다는 뜻이다.
    당연히 같거나 작으면도 있다.

     

    ClearDepthStencilView(), OMSetRenderTargets, Apply DepthStencilState

    더보기
    bool	KCore::PreRender() {
        float ClearColor[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; //red,green,blue,alpha
        m_pImmediateContext->ClearRenderTargetView(m_pRenderTargetView, ClearColor);
    
        m_pImmediateContext->ClearDepthStencilView(
            m_DepthStencilView,
            D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
        m_pImmediateContext->OMSetRenderTargets(1,
            &m_pRenderTargetView, m_DepthStencilView);
    
        ApplyDSS(m_pImmediateContext, KState::g_pDSState);
    
        return true;
    }

     

    매번 렌더링하는 Render 함수 전에 PreRender()에서
    이전 화면을 깨끗이 지우는 역활을 하는 RenderTargetView() 있다.
    DepthStencilView도 이와 같은 뷰라고 했듯이 지워줘야한다.
    ClearDepthStencilView를 하고,
    마지막 파이프라인 단계인 OMSetRenderTargets에서 뎁스스텐실뷰 포인터를 넣어준다.
    마지막으로 State를 적용해주면 끝이다.

     

    결과

     

    가장 뒤에 있는 박스가 앞에 있는 박스로 인해 가려진 모습이다.

     

     

    참고 : ChiliTomatoNoodle

    https://www.youtube.com/watch?v=C0x87s-dTdE&list=PLqCJpWy5Fohd3S7ICFXwUomYW0Wv67pDD&index=22 

     

    COMMENT
     
    12
    28
    더보기
    bool DrawTestTriangle()
    	{
    		namespace wrl = Microsoft::WRL;
    		HRESULT hr = S_OK;
    		struct Vertex
    		{
    			struct {
    				float x;
    				float y;
    			}pos;
    
    			struct {
    				unsigned char r;
    				unsigned char g;
    				unsigned char b;
    				unsigned char a;
    			}color;
    		};
    		const Vertex vertices[] =
    		{
    			{ 0.0f,0.5f,255.0,0,0 },
    			{ 0.5f,-0.5f,0,255.0,0 },
    			{ -0.5f,-0.5f,0,0,255,0 },
    			{ -0.3f,0.3f, 0,255,0,0 },
    			{ 0.3f,0.3f,0,0,255,0 },
    			{ 0.0f,-0.8f,255,0,0,0 },
    		};
    		wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
    		D3D11_BUFFER_DESC bd;
    		bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    		bd.Usage = D3D11_USAGE_DEFAULT;
    		bd.CPUAccessFlags = 0u;
    		bd.MiscFlags = 0u;
    		bd.ByteWidth = sizeof(vertices);
    		bd.StructureByteStride = sizeof(Vertex);
    
    		D3D11_SUBRESOURCE_DATA sd;
    		ZeroMemory(&sd,sizeof(D3D11_SUBRESOURCE_DATA));
    		sd.pSysMem = vertices;
    		hr=g_pd3dDevice->CreateBuffer(&bd, &sd, pVertexBuffer.GetAddressOf());
    		if (FAILED(hr)) return false;
    		const UINT stride = sizeof(Vertex);
    		const UINT offset = 0u;
    		//배열이 들어갈수있다.
    		m_pImmediateContext->IASetVertexBuffers(0u, 1u, pVertexBuffer.GetAddressOf(), &stride, &offset);
    
    		const unsigned short indices[] =
    		{
    			0,1,2,
    			0,2,3,
    			0,4,1,
    			2,1,5,
    		};
    		wrl::ComPtr<ID3D11Buffer>pIndexBuffer;
    		D3D11_BUFFER_DESC ibd = {};
    		ibd.BindFlags = D3D11_BIND_INDEX_BUFFER;
    		ibd.Usage = D3D11_USAGE_DEFAULT;
    		ibd.CPUAccessFlags = 0u;
    		ibd.MiscFlags = 0u;
    		ibd.ByteWidth = sizeof(indices);
    		ibd.StructureByteStride = sizeof(unsigned short);
    		D3D11_SUBRESOURCE_DATA isd;
    		isd.pSysMem = indices;
    		hr = g_pd3dDevice->CreateBuffer(&ibd, &isd, pIndexBuffer.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		m_pImmediateContext->IASetIndexBuffer(pIndexBuffer.Get(),DXGI_FORMAT_R16_UINT,0);  
    
    		wrl::ComPtr<ID3DBlob> pBlob;
    
    		//1. 픽셀 셰이더 생성
    		wrl::ComPtr<ID3D11PixelShader> pPixelShader;
    		hr=D3DReadFileToBlob(L"PixelShader.cso", pBlob.GetAddressOf());
    		if (FAILED(hr)) return false;
    		hr = g_pd3dDevice->CreatePixelShader(pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(), nullptr, pPixelShader.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		//2. 바인드 픽셀 셰이더
    		m_pImmediateContext->PSSetShader(pPixelShader.Get(), nullptr, 0u);
    
    		//3. 버텍스 셰이더 생성
    		wrl::ComPtr<ID3D11VertexShader> pVertexShader;
    		hr=D3DReadFileToBlob(L"VertexShader.cso", pBlob.GetAddressOf());
    		if (FAILED(hr)) return false;
    		hr = g_pd3dDevice->CreateVertexShader(pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(), nullptr, pVertexShader.GetAddressOf());
    		if (FAILED(hr)) return false;
    
    		//4. 바인드 버텍스 셰이더
    		m_pImmediateContext->VSSetShader(pVertexShader.Get(), nullptr, 0u);
    
    		//5. Input 버텍스 레이아웃 (2D 위치만)
    		wrl::ComPtr<ID3D11InputLayout> pInputLayout;
    
    		const D3D11_INPUT_ELEMENT_DESC layout[] =
    		{
    			{"POSITION",0,DXGI_FORMAT_R32G32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA, 0},
    			{"COLOR",0,DXGI_FORMAT_R8G8B8A8_UNORM,0,8,D3D11_INPUT_PER_VERTEX_DATA, 0}
    		};
    		hr = g_pd3dDevice->CreateInputLayout(layout, _countof(layout),
    			pBlob->GetBufferPointer(),
    			pBlob->GetBufferSize(),
    			pInputLayout.GetAddressOf()
    		);
    		if (FAILED(hr)) return false;
    
    		//6. 바인드 버텍스 레이아웃
    		m_pImmediateContext->IASetInputLayout(pInputLayout.Get());
    
    		m_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    
    		m_pImmediateContext->DrawIndexed((UINT)(std::size(indices)),0u,0u);
    		
    		return true;
    	};
    //vs
    struct VS_OUTPUT
    {
    	float3 color : Color;
    	float4 pos : SV_Position; //no user set name
    };
    
    VS_OUTPUT main(float2 pos : POSITION, float3 color : Color)
    {
    	VS_OUTPUT Output;
    	Output.pos = float4(pos.x, pos.y, 0.0f, 1.0f);;
    	Output.color = color;
    	return Output;
    }
    
    //ps
    struct PS_INPUT
    {
    };
    
    float4 main(float3 color : Color) : SV_Target
    {
    	return float4(color,1.0f);
    }

     

     

    버텍스버퍼,인덱스 버퍼을 생성, 바인드->

    PS, VS 셰이더 생성, 바인드-> 버텍스 레이아웃 생성 바인드

    인덱스로 Draw 하기까지의 과정이다.

     

    다렉을 사용해 한 함수로만 삼각형을 뿌리는 걸 만들어봤다.

    전체적인 흐름을 읽기에 편하기 때문이다.

     

    덕분에 애매하게 넘어갔던 것들을 이해할수 있었다.

     

    특히, ID3D11InputLayout, 시멘틱스와 버텍스 셰이더의 입력셰이더

     

    시멘틱스를 비교함으로 데이터를 타당한지 검증하는 작업이나

     

    셰이더에서 전역변수로 사용하기 위해 상수버퍼가 필요하는것 등 이해하게 되었다.

     

    COMMENT
     
    12
    28

    https://docs.microsoft.com/ko-kr/cpp/cppcx/wrl/comptr-class?view=msvc-170 

     

    ComPtr 클래스

    자세한 정보: ComPtr 클래스

    docs.microsoft.com

    COM 객체를 위한 스마트 포인터

    다렉을 하다보면 포인터를 많이 쓰게 되는데, COM 객체를 효율적으로 관리하기위해 ComPtr를 쓴다.

    스마트 포인터처럼 일일히 릴리즈할 필요도 없음 STL의 unique_ptr<T>와 상당히 유사하다.

     

    #include <wrl.h>

    namespace Microsoft::WRL

     

    주요 메소드

    - Get - 포인터를 가져옴

    - GetAddressOf - 주소값을 가져옴

    - ReleaseAndGetAddressOf

    - Reset

    - Swap

    - CopyTo

     

    예시

     

    COMMENT
     
    12
    27

    https://wiki.winehq.org/List_Of_Windows_Messages

     

    List Of Windows Messages - WineHQ Wiki

    "What is the window message numbered x?" There is no list mapping numbers to symbolic message names in MSDN. The table below provides the mappings. Hex Decimal Symbolic 0000 0 WM_NULL 0001 1 WM_CREATE 0002 2 WM_DESTROY 0003 3 WM_MOVE 0005 5 WM_SIZE 0006 6

    wiki.winehq.org

     

    Window MSDN에는 메세지 리스트가 없다.

     

    위 사이트를 참고하면 테이블로 볼 수 있다.

     

     

    이전 포스팅에서 윈도우는 메시지 기반 프로그램이라고 했는데,

    윈도우는 메시지를 큐로 담아 메세지를 해석한다. 우리가 만든

    WndProc로 관리하고 나머지는 윈도우운영체제가 알아서 다루도록

    DefWndProc 즉 기본디폴트윈도우Proc로 보내줘야한다.

    위 코드는 그런 내용을 담고 있다.

     

    msg에서 WPARAM, LPARAM 이 있는데

    키보드 입력 메세지로는 LPARAM이 키상태 (누른 상태, 뗀 상태)를 나타내고

    WPARAM이 키 종류를 나타낸다. (F 키) 

     

    SetWindowText()로는 창의 이름을 바꿀 수 있다.

     

    결과

     

    COMMENT
     
    12
    24

     

    'LOD ?'

    LOD는 Level Of Detail으로 단계에 따라서

    메시의 모델링 데이터의 정밀도를 조절하는 것이다.

    게임에서 최적화를 위해서 거리에 대응해 LOD를 사용한다.

     

    'LOD의 종류(정적 LOD 동적LOD)'

    첫번째, 정적 LOD는 처음부터 메시의 정밀도가 정해져 있고

    이를 카메라와의 거리에 따라서 단계별로

    버퍼의 교체를 통해 출력하는 것이다.

    이미 메시의 정밀도가 이미 정해져 있어서

    연산이 간단해 속도가 빠르다

    . 하지만 메시를 추가적인 메모리를 가지고

    있어야 하며 단계가 급격히 변화해 튀는 현상이 있다.

     

    두 번째, 동적 LOD는 실시간으로 메시의 정밀도를 변화시키는 기법이다.

    자연스럽게 LOD가 이루어지기 때문에 튀는 현상이 적고,

    낭비되는 메모리도 없지만 계속해서 연산을 해야 되기 때문에

    상대적으로 속도가 느리다는 단점이 있다.

     

    방사형 정적LOD 결과물

     

    카메라가 멀어져 있을때 최상단의 패치를 보여준다.

     

    거리에 따라서 두개의 패치(각 16개의 정점버퍼)가 포함된 외부파일과

    공유되는 인덱스 버퍼로 총 3단계의 LOD를 보여주고 있음

     

    "지형이 더 가까울 수록 정밀하게 표현되는 것을 볼 수가 있다."

     

    앞서 패치라고 말을 했는데 패치란 단계별 메시의 단위를 패치라고 한다.

    패치는 카메라로부터의 거리를 기반으로 적용되기 때문에

    이웃 노드들과의 LOD레벨이 다를 수밖에 없다.

    이웃 정점들과 패치의 정점이 공유되지 못하면서 "균열"이 일어나게 된다.

     

    이를 방지하기 위해서 균열이 일어날 경우의 수를 알아야 한다.

    총 16개의 경우의 수가 있다. 한 개의 정수형 변수에 저장하기 위해서 8421코드를 사용하였다.

    8421 코드는 4비트의 2진수로 0000~1111로 최대 16개의 경우의 수를 표현해주는 역할이다.

     

    해당 노드의 LOD 레벨이 이웃레벨의 LOD레벨보다 크다면

    각 방향에 해당하는 8421코드를 더해서 조합을 한다.

    그리고 해당하는 버퍼에 넣어주는 알고리즘이다.

     

    BCD(Binary-Coded Decimal) 코드: 8421 코드

     

    더보기
    bool	KQuadtree::Render(ID3D11DeviceContext* pContext, KVector3* vCamera)
    {		
    	for (int iNode = 0; iNode < m_pLeafList.size(); iNode++)
    	{
    		int iLodLevel = 0;
    		float fDistance = (m_pLeafList[iNode]->m_vCenter - *vCamera).Length();
    		if (fDistance < 50.0f)
    		{
    			m_pLeafList[iNode]->m_iLodLevel = 2;
    		}
    		else if (fDistance < 100.0f)
    		{
    			m_pLeafList[iNode]->m_iLodLevel = 1;
    		}		
    		else
    			m_pLeafList[iNode]->m_iLodLevel = 0;
    	}
    	for (int iNode = 0; iNode < m_pLeafList.size(); iNode++)
    	{
    		int iRenderCode = 0;
    		// 동서남북
    		if (m_pLeafList[iNode]->m_NeighborList[0] &&
    			m_pLeafList[iNode]->m_iLodLevel < m_pLeafList[iNode]->m_NeighborList[0]->m_iLodLevel)
    		{
    			iRenderCode += 2;
    		}
    		if (m_pLeafList[iNode]->m_NeighborList[1] && 
    			m_pLeafList[iNode]->m_iLodLevel < m_pLeafList[iNode]->m_NeighborList[1]->m_iLodLevel)
    		{
    			iRenderCode += 8;
    		}
    		if (m_pLeafList[iNode]->m_NeighborList[2] && 
    			m_pLeafList[iNode]->m_iLodLevel < m_pLeafList[iNode]->m_NeighborList[2]->m_iLodLevel)
    		{
    			iRenderCode += 4;
    		}
    		if (m_pLeafList[iNode]->m_NeighborList[3] && 
    			m_pLeafList[iNode]->m_iLodLevel < m_pLeafList[iNode]->m_NeighborList[3]->m_iLodLevel)
    		{
    			iRenderCode += 1;
    		}
    		UINT iNumIndex = 0;
    		ID3D11Buffer * pRenderBuffer = nullptr;
    		UINT iLodLevel = m_pLeafList[iNode]->m_iLodLevel;
    		if (m_pLeafList[iNode]->m_iLodLevel ==  0)
    		{
    			iNumIndex = m_LodPatchList[iLodLevel].IndexList[iRenderCode].size();
    			pRenderBuffer = m_LodPatchList[iLodLevel].IndexBufferList[iRenderCode];
    		}
    		else if (m_pLeafList[iNode]->m_iLodLevel == 1)
    		{
    			iNumIndex = m_LodPatchList[iLodLevel].IndexList[iRenderCode].size();
    			pRenderBuffer = m_LodPatchList[iLodLevel].IndexBufferList[iRenderCode];
    		}
    		else
    		{
    			iNumIndex = m_IndexList.size();
    			pRenderBuffer = m_pIndexBuffer;
    		}
    	}
    	return true;
    }

     

    COMMENT
     
    1 2 3 4 5 6 7 8 9