2024년 7월 23일 화요일

뷰 매트릭스

 

실시간 렌더링에서 플레이어,

즉 카메라가 바라보는 방향과 위치의 정보를 담고 있는 행렬을

뷰 매트릭스라 한다.




일단 왼손 좌표계와 오른손 좌표계가 있다.



이 둘의 차이는 그냥 Z축이 반대다

그래서 뷰 매트릭스를 생성하기 위해 두 가지 함수가 있는데

XMMatrixLookAtLH 랑 

XMMatrixLookAtLR 로


카메라의 위치, 바라보는 타겟의 위치, 업 벡터 세개를 매개변수로 받아

뷰 매트릭스로 바꿔준다.



근데 주로 LH를 쓰고 LR는 거의 쓰질 않는데

아마 다른 엔진이나 OpenGL 같이 오른손 좌표계를

쓰는 프로젝트에 이식하거나 할 수 있으니

일단 만들어 둔게 아닐까 생각함




카메라의 위치는 0, 0, -10에 두고

카메라 회전 각도는 0, 0, 0이니

이를 뷰 매트릭스로 변환하기 위해서는

먼저 회전 방향 xyz를 라디안으로 바꿔준다.




pitch, yaw, roll 요 셋은 각각 x, y, z 회전축의 방향 값이고

m_rotation 의 x, y, z 회전 방향 값을 라디안으로 바꿔준 값이다.

XMMatrixRotationRollPitchYaw 함수를 사용하면 회전값을 매트릭스로 만들어 주는데



이 회전 매트릭스는 카메라의 회전방향에 맞춰

업 벡터와 가르키는 방향을 회전시키는 연산을 할때 씀



4x4 회전 행렬에 저장되는 값은 요렇게 되는데

이건 0, Y, 0  즉 y값을 기준으로 회전하는 행렬이다.

연산 원리만 쉽게 설명하기 위해 이렇게 했는데

 x랑 z까지 포함하면 식이 엄청 길어짐





예를 들어 y축을 30도 만큼 회전 시킨 행렬을 생성하게 되면

이런 값을 갖게 되는 것이다.

sin cos 30도가 머였는지 이젠 기억 안나도

괜찮다 전부 컴터가 연산해주니까





그리고 이걸 타겟 좌표랑 업 벡터를 행렬과 곱연산 해주는데

그럼 y축 30도 회전시킨 LookAtVector는 이렇게 연산된다.





실제로 값도 맞게 나온다.

UpVector는 설명할것도 없고 동일하게 회전되는데

y축만 회전시켰으니 그대로 0,1,0이 됨



마무리로 가르키는 방향 좌표를 카메라 포지션만큼 더하면 됨


이제 결과들을 viewMatrix로 생성하면 끝이다

2024년 7월 16일 화요일

Python - Easy OCR

 

예전에 재밌어보이는 라이브러리를 하나 찾았는데

OCR : Optical Character Recognition

광학 문자 인식으로 이미지에 있는 텍스트를 문자열로 변환하는 기능인데

쓸만한 라이브러리로는 EasyOCR 이랑 테서렉트가 있음



두 개의 차이점으로 Easy 는 학습이 안돼고 테서렉트는 가능한거로 앎

코드는 파이참에서 테스트 했으며 터미널로 필요한 라이브러리를 설치하면 된다.



프로젝트를 생성하면 왼.아래쪽에 툴 윈도우가 있으며

도구중에서 터미널을 선택하여 명령어로 설치하면 된다.




설치해야 할 라이브러리는 총 두가지로 

이미지를 읽을 수 있는 OpenCV와 EasyOCR를 설치하면 된다.


OpenCV

pip install opencv-python 이건 GUI기능이 포함된거고

pip install opencv-python-headless 단순히 이미지 처리만 원한다면 

좀 더 가벼운 headless를 설치하면 된다.


EasyOCR

pip install easyocr



소스 코드

import cv2
import easyocr

reader = easyocr.Reader(['ko'], gpu=False)
src = cv2.imread("../../Data/Sample2.png", cv2.IMREAD_COLOR)

result = reader.readtext(src)
for (bbox, text, prob) in result:
    print(f"Text: {text}, Probability: {prob:.2f}")


GPU를 사용하지 않고 변환하는데 많이 느리다.

사용하고 싶으면 CUDA를 따로 설치한다음 gpu=True로 해주면 됨

 

테스트에 사용한 이미지는 이렇게 두가지로

투명 배경, 하얀 배경 둘다 잘 변환 됨



텍스트 뒤 Probability는 정확도로 0~1사이 백분율을 나타냄

만약 정확도를 더 높이고 싶으면 테서렉트를 사용하거나

이미지 처리를 통해 높이는 방법밖에 없음

2024년 7월 15일 월요일

Java - 채팅 프로그램

 

예전에 만든 소켓 통신 테스트 코드가 있길래

혹시 몰라 올려봄


프로그램은 클라이언트용, 서버용 두 종류가 있고

서버는 클라이언트 프로그램으로 부터 받은 메시지를 처리하는 프로그램,

클라이언트는 서버 프로그램으로 연결을 시도하여 성공시 메시지를 전송 할 수 있음




클라이언트는 아이피와 포트, 아이디를 입력하여 접속이 가능한데

어차피 같은 네트워크의 컴퓨터나 하나의 컴퓨터에서만 테스트 가능하므로

루브백 아이피를 입력하면 된다. 127.0.0.1




클라를 두개 실행 시키고 테스트 해보면

방만들기 버튼으로 채팅방을 열고 방 선택해서 참여하면

서로 채팅을 주고 받을 수 있음



전체 소스코드

https://github.com/h117562/ChatProgram






2024년 7월 11일 목요일

DirectX Tutorial - UI & 입력 시스템



모델 불러올 때 마다

파일경로를 목표에 넣고 F5 스위치..

파일경로를 목표에 넣고 F5 스위치..

파일경로를 목표에 넣고 F5 스위치..

실패하면 또 코드 보고 어디 잘못했는지 확인하고 수정하고...

화나죠?






그래서 이런 UI같은 화면을 구현해서 버튼으로

파일경로를 불러오는 기능을 구현하고자 함



우선 마우스 클릭을 통해 버튼이 눌렸는지 확인하기 위해서

SystemClass의 콜백함수에서 윈도우 메시지를 처리하여 WM_KEYDOWN이나

KEYUP 메시지를 받았을 경우 InputClass의 멤버 변수를 true또는 false로 바꾸며

마우스나 키보드를 눌렀는지 확인하고 lparam의 마우스 좌표를

저장하여 다른 클래스에서 사용할 수 있게 함수 리턴으로 전달하였음


LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
	switch (msg)
	{
	case WM_KEYDOWN:
	{
		m_inputClass->KeyPressed(wparam);
		break;
	}
	case WM_KEYUP:
	{
		m_inputClass->KeyReleased(wparam);
		break;
	}
	case WM_MOUSEMOVE:
	{
		m_inputClass->SetMousePosition(LOWORD(lparam), HIWORD(lparam));
		break;
	}
	case WM_LBUTTONDOWN:
	{
		m_inputClass->SetMousePosition(LOWORD(lparam), HIWORD(lparam));
		m_inputClass->KeyPressed(LBUTTON);
		break;
	}
	case WM_LBUTTONUP:
	{
		m_inputClass->SetMousePosition(LOWORD(lparam), HIWORD(lparam));
		m_inputClass->KeyReleased(LBUTTON);
		break;
	}
	case WM_RBUTTONDOWN:
	{
		m_inputClass->SetMousePosition(LOWORD(lparam), HIWORD(lparam));
		m_inputClass->KeyPressed(RBUTTON);
		break;
	}
	case WM_RBUTTONUP:
	{
		m_inputClass->SetMousePosition(LOWORD(lparam), HIWORD(lparam));
		m_inputClass->KeyReleased(RBUTTON);
		break;
	}
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}

요렇게


InputClass의 포인터 변수를 매개변수로 전달하여 다른 클래스에서도

빠르고 쉽게 어떤키가 눌렸는지 확인 가능



그럼 마우스 위치를 알아냈으니 이를 버튼을 가르키는지 판별하기 위해서

레이캐스팅을 사용하였음


레이 캐스팅(Ray Casting)은 3D 그래픽스와 게임 개발에서 중요한 기법으로, 가상의 광선을 발사하여 그 광선이 장면의 어떤 객체와 교차하는지를 결정하는 방법입니다. 이를 통해 사용자는 마우스 클릭 등의 입력을 통해 3D 공간의 객체를 선택하거나, 시점 내의 물체를 확인할 수 있음...


ButtonClass의 레이캐스팅 하는 함수

//레이캐스팅을 이용하여 마우스가 버튼위에 올라가 있는지 확인하기 위한 함수
bool ButtonClass::CollisionCheck(D3DClass* pD3DClass, CameraClass* pCameraClass, InputClass* pInputClass)
{
	bool result;

	XMMATRIX viewMatrix, projectionMatrix, viewProjectionMatrix;
	XMMATRIX inverseViewProjection;
	XMFLOAT2 mousePoint, screenSize;
	XMVECTOR rayOrigin, rayDirection;
	
	pInputClass->GetMousePosition(mousePoint);//마우스 위치를 받아옴
	pD3DClass->GetScreenSize(screenSize);//프로그램 사이즈를 받아옴

	mousePoint = XMFLOAT2(
		((2.0f * mousePoint.x) / screenSize.x) - 1.0f,
		((-2.0f * mousePoint.y) / screenSize.y) + 1.0f
	);//마우스 위치를 정규화

	//UI의 경우 0,0,-10 위치의 기본 뷰 매트릭스에서 렌더링 되니 간단한 연산만 하면 됨
	pCameraClass->GetBaseViewMatrix(viewMatrix);//기본 시점 뷰 매트릭스
	pD3DClass->GetOrthoMatrix(projectionMatrix);//직교 투영 매트릭스

	viewProjectionMatrix = XMMatrixMultiply(viewMatrix, projectionMatrix);//뷰, 투영 서로 행렬 곱
	inverseViewProjection = XMMatrixInverse(NULL, viewProjectionMatrix);//역행렬을 구함

	rayOrigin = XMVectorSet(mousePoint.x, mousePoint.y, 0.0f, 1.0f);//레이 시작
	rayDirection = XMVectorSet(mousePoint.x, mousePoint.y, 1.0f, 1.0f);//레이 끝

	rayOrigin = XMVector3Transform(rayOrigin, inverseViewProjection);//뷰.투영 역행렬로 벡터 변환
	rayDirection = XMVector3Transform(rayDirection, inverseViewProjection);//뷰.투영 역행렬로 벡터 변환

	rayDirection = XMVectorSubtract(rayDirection, rayOrigin);//레이 끝에서 시작을 빼서 방향을 구함
	rayDirection = XMVector3Normalize(rayDirection);//레이 방향을 정규화

	float dist = 0.0f;//충돌 거리를 받는 변수
	XMVECTOR v1, v2, v3;

	//XMFLOAT3을 XMVECTOR로 변환
	v1 = XMLoadFloat3(&m_vertices[0].position);
	v2 = XMLoadFloat3(&m_vertices[1].position);
	v3 = XMLoadFloat3(&m_vertices[2].position);

	//충돌 체크 함수
	result = TriangleTests::Intersects(rayOrigin, rayDirection, v1, v2, v3, dist);
	if (result)
	{
		return true;
	}

	//XMFLOAT3을 XMVECTOR로 변환
	v1 = XMLoadFloat3(&m_vertices[3].position);
	v2 = XMLoadFloat3(&m_vertices[4].position);
	v3 = XMLoadFloat3(&m_vertices[5].position);

	//충돌 체크 함수
	result = TriangleTests::Intersects(rayOrigin, rayDirection, v1, v2, v3, dist);
	if (result)
	{
		return true;
	}

	return false;
}

간단히 말해 프로그램 위 마우스 좌표를 3D 월드 공간으로 변환하여

어느 방향을 가르키는지 레이저를 쏴서 이게 네모에 적중하는지 확인하는 기능



버튼은 삼각형 두개로 이루어진 사각형으로,

위치와 크기를 매개변수로 받아 정점으로 만들어 저장함


대충 이렇게 배치 하고 UI 렌더링 할때만 알파 블랜딩을 켰다가 끔

안그러면 사각형 나머지 부분이 검정이나 하얗게 보이게 됨


돋보기는 모델을 불러오는 버튼이고

밑에는 모델을 좌우 회전시키는,

별은 + - 로 할려고 했는데 안보여서 그냥 대충 이미지 넣고

확대 축소하는 버튼임



참고로 그림은 아무 사이트에서 구해서 포샵으로 잘라냈음

원래 직접 그려보려고 시도하다가 답답해서 그냥 지우고 롤하러 감..

희생양들 ㅠㅠ



버튼 이미지 출처

 https://kr.freepik.com/free-vector/web-buttons-circle-glass-icons-game_26568787.htm#query=ui%20button&position=40&from_view=keyword&track=ais_user&uuid=ee9c7236-e7ce-4a18-9323-1c3145a9bb19




마우스가 버튼 위에 존재하며, 동시에 LBUTTON이 true일 때

버튼이 눌렸다고 리턴하고 이전 상태와 비교해서 

LBUTTON이 true -> false 로 바뀌는 순간에만 버튼이 눌렸다고 리턴함


물론 버튼을 누르는 도중에 버튼 영역을 이탈하면 전부 초기상태로 되돌리기





UI는 두 종류가 있는데 

디버깅 정보를 표시하는 UI 와

이번에 만든 모델 불러오는 UI


근데 클래스 이름이 ModelInspector 인 이유는

원래는 모델의 자세한 정보도 표시되게 하고 싶었는데

UI 디자인 하는 센스도 구리고 스크롤 바랑 페이지를 구현하기도 오래 걸릴거 같아

그냥 파일 불러오고 회전, 확대하는 버튼만 넣었음

 




돋보기 누르면 파일 경로를 가져올 수 있게 대화상자를 띄워 디렉토리 탐색



대화상자를 띄우는 부분임

void FbxLoader::OpenFileDialog(HWND hwnd)
{
	OPENFILENAMEA  ofn;       // 공통 대화 상자 구조체

	if (m_filepath)
	{
		delete[] m_filepath;
		m_filepath = nullptr;
	}

	m_filepath = new char[MAX_PATH];//파일명은 전체 경로를 포함하여 최대 259 글자

	ZeroMemory(&ofn, sizeof(OPENFILENAMEA));//구조체 초기화
	ofn.lStructSize = sizeof(OPENFILENAMEA);//구조체 크기 설정
	ofn.hwndOwner = hwnd;
	ofn.lpstrFile = m_filepath; //파일 경로를 저장할 버퍼
	ofn.lpstrFile[0] = '\0';
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrFilter = "All\0*.*\0Text\0*.TXT\0";//파일 필터 설정
	ofn.nFilterIndex = 1; //필터 인덱스
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;//대화 상자 플래그

	// 파일 열기 대화 상자 표시
	GetOpenFileNameA(&ofn);//char형으로 파일 경로를 받기 위함 wchar는 GetOpenFileNameW로

	return;
}

MFC 안쓰고 하는 방법 좀 알아보는데 

많은 도움을 준 채찍피티에 박수




GetOpenFileName 함수는 프로젝트 속성이 멀티바이트인지 유니코드인지 여부에 따라

char, wchar_t로 파일경로를 저장하는데 

GetOpenFileNameA는 그런거 무시하고 char로,

GetOpenFileNameW는 wchar_t로 저장



결과




편집해서 로딩이 빠른 것 처럼 보이는데

실제로는 엄청 느림

vector 써서 그럴지도

그래도 메모리 해제가 쉽고 안전해서 바꾸기엔 좀 그렇다


참고로 WASD로 이동가능


깃 링크 : https://github.com/h117562/Tutorial_UI

c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데 이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는 함수가 없어서 조금 아쉬운 애다. std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든 start 함...