C++에서 WinAPI와 Direct2D를 이용해 Rendering Pipeline을 설계한 2D 리듬게임 프로젝트
2023 University(한국공학대학교) Win32 Project (2_1 Grade) 2D Pipeline Process Develop Project
- MFC/ATL // Visual Studio Package
- WinAPI // Windows Graphic API
- Direct2D // GPU Graphic API
- FMOD // Sound
- Eigen3 // Math:Matrix
https://youtu.be/temw8YyZNhk
Node 작업 Google Sheet
- Jin Hyeon Seo (Clrain) : Main Develop & Main Art & Main Design
- Jung Jin Bong : Music Node Data Maker
접기/펼치기
Ctrl + Q : 종료
Ctrl + 1 : 타겟 프레임 30 설정
Ctrl + 2 : 타겟 프레임 60 설정
Ctrl + 3 : 타겟 프레임 144 설정
Ctrl + 4 : 타겟 프레임 244 설정
Ctrl + 0 : 프레임 제한 설정/해제
Y : 게임 일시정지
U : 게임 일시정지 해제
I : 게임 정지
L : 오토 공격 켜기/끄기
로고 화면 + S : 로고 스킵.
접기/펼치기
#include <atlImage.h>
을 찾을 수 없을 경우 (Not Found). Visual Studio Installer에서 개별 구성요소 -> MFC/ATL 모듈 구성 요소를 설치할 것.
Logic Pipeline & Render Pipeline & Debug System Develop
- Debug 창과 MicroSecond 단위로 측정되는 deltaTime log.
- Update PipeLine이 개발된 상태.
- Camera와 공간상 좌표계를 표현하고 활용하기 위해 벡터와 행렬의 연산을 도와줄 Eigen 라이브러리를 기용.
Sprite Rendering pipeline 계산 절차 설계
3단계 프로세스에 걸쳐서 Rendering 과정을 수립. 논리적 사고 흐름을 정리.
- 원시데이터를 스케일링, 컷팅, 플립을 계산하여 StretchBlt 수행
- 행렬 매트릭스로 3개의 벡터를 곱. [ cos(angle), -sin(angle)] [ sin(angle), cos(angle)] v1 : (-stretch * pivot) 뒤로 당겨주는 벡터. v2 : (stretchW, 0) v3 : (0, stretchH)
렌더링 소스코드 (접기/펼치기)
HDC m_hDC = img.GetDC();
float stretchW = sizex;
float stretchH = sizey;
// -- 1단계 --
HDC m_hDC2 = CreateCompatibleDC(m_hDC);
HBITMAP bmp = CreateCompatibleBitmap(m_hDC, stretchW, stretchH);
SelectObject(m_hDC2, bmp);
float filterX = imageW * imageOffsetx;
float filterY = imageH * imageOffsety;
float filterW = imageW * imageScalex;
float filterH = imageH * imageScaley;
StretchBlt(m_hDC2, 0, 0, stretchW, stretchH, m_hDC,
filterX + (flipx == 1 ? 0 : filterW - 1),
filterY + (flipy == 1 ? 0 : filterH - 1),
(flipx == 1 ? 1 : -1)* filterW, (flipy == 1 ? 1 : -1)* filterH, SRCCOPY);
// -- 2단계 --
float bmp3Size = sqrt(stretchW * stretchW + stretchH * stretchH);
HDC m_hDC3 = CreateCompatibleDC(m_hDC);
HBITMAP bmp3 = CreateCompatibleBitmap(m_hDC, bmp3Size * 2, bmp3Size * 2);
SelectObject(m_hDC3, bmp3);
Eigen::Matrix3d rotateM;
rotateM <<
cos(angle * D2R), -sin(angle * D2R), 0,
sin(angle* D2R), cos(angle* D2R), 0,
0, 0, 1;
Eigen::Vector3d v1, v2, v3;
v1 << stretchW, 0, 1;
v2 << 0, stretchH, 1;
v3 << -stretchW * pivotx, -stretchH * pivoty, 1;
v1 = rotateM * v1;
v2 = rotateM * v2;
v3 = rotateM * v3;
POINT a[3] = {
{bmp3Size + v3.x(), bmp3Size + v3.y()},
{bmp3Size + v3.x() + v1.x(), bmp3Size + v3.y() + v1.y()},
{bmp3Size + v3.x() + v2.x(), bmp3Size + v3.y() + v2.y()},
};
::PlgBlt(m_hDC3, a, m_hDC2,
0, 0,
stretchW, stretchH,
NULL, NULL, NULL);
// -- 3단계 --
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0xff;
bf.AlphaFormat = AC_SRC_ALPHA;
bResult = ::AlphaBlend(hDC,
x - bmp3Size, y - bmp3Size,
bmp3Size * 2, bmp3Size * 2,
m_hDC3, 0, 0,
bmp3Size * 2, bmp3Size * 2, bf);
DeleteObject(m_hDC2);
DeleteObject(bmp);
DeleteObject(m_hDC3);
DeleteObject(bmp3);
img.ReleaseDC();
Camera 기반 렌더링 시스템 & Hierarchy 구조 도입
TargetFrame System을 도입. Frame Lock을 걸면 해당 프레임 이상 솓구치지 않는 것을 확인했다.
카메라 렌더링 시스템을 개발했다. 근데 PlgBit의 레스터라이즈 과정이 CPU에서 진행된다는 매우 치명적인 단점이 있었다. 그래서 angle이 0도일땐 괜찮지만... angle이 조금이라도 들어가면 매우매우 렉걸리는 상황에 봉착했고, 이 프레임 문제를 해결해야하는 과제가 생겼다.
렌더링 API를 GDI에서 Direct2D로 전면 교체
이슈 발생 -> 원인 분석 -> 해결방안 모색 -> 적정 기술 학습 -> 기술 도입 -> 기술 호환성 -> 사후 리뷰
- PlgBit의 성능이 매우 좋지 않아 1920x1080 해상도에서 1~4프레임정도 나온다는 것을 확인했다.
- PlgBit는 오래된 API인 GDI 1세대를 기반으로한 함수이다.
- 픽셀 레스터라이즈 과정을 CPU로 수행하기 때문에 속도가 느릴 수 밖에 없다.
- 고심끝에 Direct2D로 렌더링 시스템을 전면 교체하기로 했다.
- Direct2D는 GDI 3세대 기술로 GPU렌더링을 지원한다.
- GDI보다 절차가 복잡하고, 기반지식이 하나도 없어서 밑바닥부터 전부 다시 배워야하는 학습장벽이 존재했다.
- 하지만, 좋은 퀄리티의 게임을 개발하기 위해서 빠르게 습득하기로 결정했다.
- 24시간 정도의 학습기간을 갈아넣은 결과 프레임이 굉장히 잘나온다. 달리진게 없어 보이지만, 내부 코드는 완전히 싹다 갈아 엎었다.
- 렌더링 부분만 뜯어내면서 기존로직과 100% 호환되게 작업했기 때문에 겉보기엔 똑같이 보인다.
- 기존 GDI라면 1~4프레임 나오겠지만, Direct2D에선 160~200프레임 이상 나오는 것을 확인했다.
API | FPS | 배수 |
---|---|---|
GDI | 1 ~ 4 | 1.0 |
Direct2D | 165 ~ 200 | 90.0 ~ 100.0 |