2D Game Programming -...

Post on 06-Nov-2019

2 views 0 download

Transcript of 2D Game Programming -...

한국산업기술대학교

게임공학과

정 내 훈

2D Game Programming

개요

2

표면

– 표시 가능한 메모리

팔레트

DirectDraw로 그리기

표면 (Surface)

3

정의 : 실제 그래픽이 그려지는 장소

– 모니터로 나오는 것은 주표면 하나 뿐

위치 : 메모리 영역

– 한 프로그램에서 여러 개의 표면을 다룰 수있다.

주표면

– 비디오 스크린 자체

표면 (Surface)

4

메모리

메인메모리

비데오메모리

표면

표면

표면

표면

표면

표면

표면

표면

표면 (Surface)

5

성질

– 표면은 임의의 크기를 가질 수 있다.

• 예외) 주표면 : 화면해상도와 일치해야 한다.

– 비디오 메모리와 시스템 메모리에 작성할 수있다.

• 비디오 메모리가 빠르다.

– 비트 깊이와 색상 공간으로 속성 정의

• 같은 속성을 갖는 경우 데이터를 교환할 수 있다.

표면 (Surface)

6

종류– 주표면

• Primary Display Surface

• 모니터에 표시되는 표면

• 한 개만 존재

– 보조 표면

• Secondary Display Surface

• 후면버퍼 (back buffer)라고도 불린다.

• 주표면과 같은 속성 (, 크기 )

• 애니메이션을 위한 다음 준비 표면

– 오프스크린 표면

• 실제 모니터와 연결되는 일이 없음

• 하드웨어 가속을 위해 이미지를 저장하는 표면

표면 (Surface)

DirectDraw에서의 표면

– 표면 자체의 포인터

• LPDIRECTDRAWSURFACE7

– 표면 속성 자료구조

• LPDDSURFACEDESC2

– 표면 생성 API

• 성공시 DD_OK

7

HRESULT CreateSurface(

LPDDSURFACE2 lpDDSurfaceDesc,

LPDIRECTDRAWSURFACE7 FAR *lplpDDSurface,

Iunknown FAR *pUnkOuter);

표면 (Surface)

DirectDraw 표면 기술자 구조체

– Surface descriptor structure

8

typedef struct _DDSURFACEDESC2

{

DWORD dwSize; // size of the DDSURFACEDESC structure

DWORD dwFlags; // determines what fields are valid

DWORD dwHeight; // height of surface to be created

DWORD dwWidth; // width of input surface

union

{

LONG lPitch; // distance to start of next line (return value only)

DWORD dwLinearSize; // Formless late-allocated optimized surface size

} DUMMYUNIONNAMEN(1);

union

{

DWORD dwBackBufferCount; // number of back buffers requested

DWORD dwDepth; // the depth if this is a volume texture

} DUMMYUNIONNAMEN(5);

union

{

DWORD dwMipMapCount; // number of mip-map levels requestde

// dwZBufferBitDepth removed, use ddpfPixelFormat one instead

DWORD dwRefreshRate; // refresh rate (used when display mode is described)

DWORD dwSrcVBHandle; // The source used in VB::Optimize

} DUMMYUNIONNAMEN(2);

DWORD dwAlphaBitDepth; // depth of alpha buffer requested

DWORD dwReserved; // reserved

LPVOID lpSurface; // pointer to the associated surface memory

...

표면 (Surface)

DirectDraw 표면 기술자 구조체

– Surface descriptor structure

9

typedef struct _DDSURFACEDESC2

{

. . . . . . .

union

{

DDCOLORKEY ddckCKDestOverlay; // color key for destination overlay use

DWORD dwEmptyFaceColor; // Physical color for empty cubemap faces

} DUMMYUNIONNAMEN(3);

DDCOLORKEY ddckCKDestBlt; // color key for destination blt use

DDCOLORKEY ddckCKSrcOverlay; // color key for source overlay use

DDCOLORKEY ddckCKSrcBlt; // color key for source blt use

union

{

DDPIXELFORMAT ddpfPixelFormat; // pixel format description of the surface

DWORD dwFVF; // vertex format description of vertex buffers

} DUMMYUNIONNAMEN(4);

DDSCAPS2 ddsCaps; // direct draw surface capabilities

DWORD dwTextureStage; // stage in multitexture cascade

} DDSURFACEDESC2;

표면 (Surface)

DDSURFACEDESC2의 용도

– 표면의 속성을 정의하거나 수정할 때 사용

– 모든 멤버를 다 정의해야 할 필요는 없음

– 필요한 멤버를 정의하고 정의된 멤버를dwFlags에 지정하면 됨

– dwSize를 지정하는 것을 잊으면 안됨

10

표면 (Surface)

dwFlag의 설정들

11

값 의미

DDSD_ALL 모든 멤버가 유효

DDSD_HEIGHT dwHeight가 유효

DDSD_WIDTH dwWidth가 유효

DDSD_PITCH lpitch가 유효

DDSD_PIXELFORMAT ddpfPixelFormat이 유효

DDSD_LPSURFACE lpSurface가 유효

DDSD_CAPS ddsCaps가 유효

DDSD_ALPHABITDEPTH dwAlphaBitDept가 유효

표면 (Surface)

ddCaps란?

– 화면에 부가적인 속성을 지정할 때 사용

– dwCaps만 사용

• dwCaps2, dwCaps3, dwCaps4는 3D용

12

typedef struct _DDSCAPS2

{

DWORD dwCaps; // capabilities of surface wanted

DWORD dwCaps2;

DWORD dwCaps3;

union

{

DWORD dwCaps4;

DWORD dwVolumeDepth;

} DUMMYUNIONNAMEN(1);

} DDSCAPS2;

표면 (Surface)

DwCaps의 설정들

13

값 의미

DDSCAPS_BACKBUFFER 표면이 후면 버퍼가 된다.

DDSCAPS_FLIP 플립이 가능하다.

DDSCAPS_FRONTBUFFER 표면이 전면 버퍼가 된다.

DDSCAPS_OFFSCREENPLAIN 표면이 오버레이, 텍스쳐, z-버퍼,

전면버퍼, 후면버퍼가 아닌 오프스크린 버퍼이다

DDSCAPS_OWNDC DC를 갖는다

DDSCAPS_PRIMARYSURFACE 주표면이다

DDSCAPS_SYSTEMMEORY 주메모리에 할당된다.

표면 (Surface)

주 표면 생성하기

14

LPDIRECTDRAW7 lpdd = NULL; // dd object

LPDIRECTDRAWSURFACE7 lpddsprimary = NULL; // dd primary surface

DDSURFACEDESC2 ddsd; // a direct draw surface description struct

if (DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)!=DD_OK)

return(0);

// set cooperation level to windowed mode normal

if (lpdd->SetCooperativeLevel(main_window_handle,

DDSCL_ALLOWMODEX | DDSCL_FULLSCREEN |

DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)!=DD_OK)

return(0);

// set the display mode

if (lpdd->SetDisplayMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP,0,0)!=DD_OK)

return(0);

// Create the primary surface

memset(&ddsd,0,sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS;

ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

if (lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL)!=DD_OK)

return(0);

표면 (Surface)

주 표면 생성하기

– 잊지 말 것

15

if (lpddsprimary!=NULL)

lpddsprimary->Release();

// release the directdraw object

if (lpdd!=NULL)

lpdd->Release();

실습

프로그램

– Prog9_1.cpp

16

메모리 주소와 픽셀의 위치

메모리와 픽셀의 대응

– 가로세로 w, h의 크기를 갖는 표면

17

(w-1, h-1)

(0,0)

메모리 주소 증가 방향 (dwBPP / 8 만큼 증가)

메모리 주소 증가 방향 (lPitch만큼 증가)

메모리 주소와 픽셀의 위치

선형 vs 비선형 메모리

– dwBPP / 8 * w = lPitch????

• 선형 메모리 : YES

• 비선형 메모리 : NO

– 하나의 가로줄의 메모리크기가 2^n형태로표현되지 않을때 lPitch가 2^n이 되면서 비선형 메모리가 될 수 있다.

• 2^n이 되면 곱하기가 아닌 shift연산으로 좌표를계산할 수 있다.

– 1024 * x = x << 10

메모리 주소와 픽셀의 위치

비선형 메모리

19

(w-1, h-1)

2^n - 1

메모리 주소 증가 방향 (dwBPP / 8 만큼 증가)

메모리 주소 증가 방향 (lPitch만큼 증가)

화면에표시되지

않는

영역

(0,0)

dwBPP / 8 * (w – 1)

2^n

0

2^n * 2

메모리 주소와 픽셀의 위치

픽셀 좌표 계산

– 픽셀의 메모리 주소• 표면의 주소 + X 좌표 * dwBPP / 8 + Y 좌표 * dwWidth *

dwBPP / 8

– 선형 메모리일 경우 만 옳음

• 표면의 주소 + X 좌표 * dwBPP / 8 + Y 좌표 * lPitch

– 픽셀의 Color 변환법

20

UCHAR *video_buffer;

// 8 BPP

// Width가 1024인 경우

video_buffer[x + 1024*y] = color;

UINT *video_buffer;

// 32 BPP

// Width가 1024인 경우

video_buffer[x + 1024*y] = color;

DirectDraw의 그래픽 API

DirectDraw는 가장 기본적인 API만 제공한다.

– 색상 설정

– 표면 메모리 접근

– HW 가속 : 채움(Filling), 블리팅 (BitBlt)

그러면 선? 원? 다각형?

– 위의 API로 각자 제작해야 한다!

팔레트 모드

과거 메모리 절약을 위해 사용하던 color

간접 지정 방식

사용할 color의 리스트를 테이블에 저장하고 실제 표면에는 리스트의 index를 저장

일반적으로 256크기의 테이블 사용

– BPP가 8이 됨

자세한 내용은 생략

픽셀 그리기

그리기 위해 필요한 사항

– 표면의 주소

– BPP, width

표면의 주소

– DDSD의 lpSurface

– lpSurface를 얻는 API : Lock

23

HRESULT Lock(LPRECT lpDestRecct,

LPDDSURFACEDESC2 lpDDSD,

DWORD dwFlags,

HANDLE hEvent);

픽셀 그리기

Lock

– 표면의 위치는 DirectX가 관리, 언제든지 이동 가능

• 따라서 사용하기 위해서는 움직이지 않게 고정(lock)시킬 필요가 있음.

24

HRESULT Lock(LPRECT lpDestRecct, // 변경하고자 하는 영역

LPDDSURFACEDESC2 lpDDSD, // 얻는 표면의 속성

DWORD dwFlags, // 얻고자 하는 속성

HANDLE hEvent); // 사용되지 않음, 반드시 NULL

픽셀 그리기

Lock의 플랙– DDLOCK_READONLY The surface locked is

readable only.

– DDLOCK_SURFACEMEMORYPTR The

surface locked returns a memory pointer to the

surface memory in lpSurface. This default action

takes place if you don’t send any flags.

– DDLOCK_WAIT If the surface can’t be locked,

wait until it can be.

– DDLOCK_WRITEONLY The surface being

locked is written to only.

25

픽셀 그리기

Unlock

– 다 그린 이후에는 반드시 Unlock을 해야 한다.

26

HRESULT Unlock(

LPRECT lpRect // pointer to rectangle to unlock

);

픽셀 그리기

픽셀을 화면에 찍는 법

– 1. 표면을 잠근다.

– 2. 디스플레이 표면의 포인터를 얻는다.

– 3. 픽셀 주소를 계산한다.

– 4. 주소에 쓴다.

– 5. 표면 잠금을 푼다.

27

픽셀 그리기

픽셀을 화면에 찍는 법

28

memset(&ddsd,0,sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

// lock the primary surface

lpddsprimary->Lock(NULL,&ddsd,

DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL);

// get video pointer

video_buffer = (UCHAR *)ddsd.lpSurface;

//.. use video pointer to write to memory

// notice the use of lPitch (linear pitch)

video_buffer[x + y*ddsd.lPitch] = col;

// unlock the surface

lpddsprimary->Unlock(NULL);

실습

프로그램

– Prog9_2.cpp

29

Color

16Bit

– 1.5.5.5 형식

• alpha 1 bit

• red, green, blue 각각 5 bits

– 5.6.5 형식

• red 5 bits

• green 6 bits (사람의 눈은 green에 가장 민감)

• blue 5 bits

– 일반적으로 99% 이상의 video card들은5.6.5 형식을 사용한다.

30

31

Color

16비트 Color 매크로

// 5.5.5 format

#define _RGB16BIT(r,g,b) ((b%32)+((g%64)<<5)+((r%32)<<11))

example)

UCHAR red = 0x08 // 0000 1000

UCHAR green = 0x05 // 0000 0101

UCHAR blue = 0x1b // 0001 1011

Result = _RGB16BIT565(red,green,blue)– (blue % 32) = 0001 1011

– (green % 64) << 5 = (0000 0101) << 5 = 1010 0000

– (red % 32) << 11 = (0000 1000) << 11 = 0100 0000 0000 0000

Result = 0100 0000 1011 1011 = (01000) (000101) (11011)

32

Color

// this builds a 16 bit color value in 1.5.5.5 format (1-bit alpha mode)

#define _RGB16BIT555(r,g,b) ((b & 31) + ((g & 31) << 5) + ((r & 31) <<

10))

example)

UCHAR red = 0x08 // 0000 1000

UCHAR green = 0x05 // 0000 0101

UCHAR blue = 0x1b // 0001 1011

Result = _RGB16BIT555(red,green,blue)– (blue & 31) = (0001 1011) & (0001 1111) = 0001 1011

– (green & 31) << 5 = (0000 0101) << 5 = 1010 0000

– (red & 31) << 10 = (0001 1000) << 10 = 0110 0000 0000 0000

Result = 0110 0000 1011 1011 = 0 (11000) (00101) (11011)

33

16bit Mode로 그리기

640x480x16 모드에서 (x, y) 위치에 (r, g, b) 색상으로 그리는 예제

memset(&ddsd,0,sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

// lock the primary surface

lpddsprimary->Lock(NULL,&ddsd,

DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,NULL);

// get video pointer

video_buffer = (USHORT *)ddsd.lpSurface;

// use video pointer to write to memory..

// notice the use of lPitch (linear pitch) and the division by 2 (>>1);

// this is needed to keep the addressing correct because you’re using USHORT

// pointers and lPitch is always in bytes

video_buffer[x + (y*ddsd.lPitch >> 1)]=

(USHORT)_RGB16BIT565(r,g,b);

// unlock the surface

lpddsprimary->Unlock(NULL);

34

Why lPitch >> 1

// pointers and lPitch is always in bytes

video_buffer[x + (y*ddsd.lPitch >> 1)]=

(USHORT)_RGB16BIT565(r,g,b);

USHORT를 사용할 때 모든 포인터 연산은 16비트로 이루어지지만, lPitch는 항상바이트로 표현되기 때문이다.

35

DEMO: PROG9_3.CPP

PROG9_2_16.cpp 와 다른 점은 무엇인가?

36

24비트나 32비트 표면

#define _RGB24BIT(a,r,g,b) ((b) + ((g) << 8) + ((r) << 16) )

24bit 는 32bit 중 최상위 8bit가 사용되지 않을 뿐이다.

따라서 bpp를 32로 하는 것이 좋다.

37

24, 32bit 이용 (1)

In Game_Init()

if (lpdd->SetDisplayMode(SCREEN_WIDTH,

SCREEN_HEIGHT,32,0,0)!=DD_OK) return(0);

38

24, 32bit 이용 (2)

Game_Main()

UINT *video_buffer = NULL;

video_buffer = (UINT *)ddsd.lpSurface;

int words_per_line = (ddsd.lPitch >> 2);

UCHAR red = rand()%256;

UCHAR green = rand()%256;

UCHAR blue = rand()%256;

video_buffer[x + (y*words_per_line)] = _RGB24BIT(red,green,blue);

39

보조표면(Secondary Surface)

보조 표면(또는 후면버퍼) 의 사용

– 부드러운 애니메이션

– 1. 주표면을 생성하고, 주표면으로부터 하나의 보조표면을 생성한다.

– 2. 주표면이 보여지는 동안 보조표면에 그린다.

– 3. 순간적으로 표면을 바꾸어서(switch or

flip), 보조표면이 주표면이 되게 하여, (그 반대로도 마찬가지) 부드러운 애니메이션을 만든다.

40

Page Flipping

41

보조표면의 생성 (1)

// DirectDraw surface description

DDSURFACEDESC2 ddsd;

// device capabilities structure, used to query for

// secondary backbuffer, among other things

DDSCAPS2 ddscaps;

LPDIRECTDRAWSURFACE7 lpddsprimary, // primary surface

lpddssecondary; // secondary backbuffer surface

// prepare to create primary surface with one backbuffer

memset((void *)&ddsd,0,sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd); // DDSURFACEDESC would work, too

// set the flags to validate both the capabilities

// field and the backbuffer count field

ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;

// you need to let dd know that you want a complex flippable surface

structure;

42

보조표면의 생성 (2)

// set flags for that

ddsd.ddsCaps.dwCaps =

DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP |

DDSCAPS_COMPLEX;

// set the backbuffer count to 1

ddsd.dwBackBufferCount = 1;

// create the primary surface

lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL);

// query for the backbuffer or secondary surface

// notice the use of ddscaps to indicate what you’re

requesting

ddscaps.dwCaps = DDSCAPS_BACKBUFFER;

// get the surface

lpddsprimary->GetAttachedSurface(&ddscaps,&lpddsback);

43

주표면과 부표면

44

Rendering Backbuffer

// used to access secondary video buffer

UCHAR *video_buffer;

// lock the secondary surface

lpddsback->Lock(NULL,&ddsd,

DDLOCK_SURFACEMEMORYPTR |

DDLOCK_WAIT,NULL);

// draw on the surface: ddsd.lpSurface and

// ddsd.lPitch are valid as before

video_buffer = (UCHAR *)ddsd.lpSurface;

// unlock the surface

lpddsback->Unlock(NULL);

45

Flipping

HRESULT Flip(

LPDIRECTDRAWSURFACE7 lpDDSurfaceOverride, // always NULL

DWORD dwFlags); // always DDFLIP_WAIT

// flip the primary and secondary surfaces

while(lpddsprimary->Flip(NULL, DDFLIP_WAIT)!=DD_OK);

46

Flipping 전후

47

오프스크린 표면(off-screen surface)

시스템 메모리나 VRAM에 모두 존재할 수 있음

주표면과 같은 색상깊이와 속성을 가지는 비트맵

하드웨어 가속 Blt()를 위해 사용

스프라이트

– 간단히 말해서 비디오 게임 화면에서 움직이는 작은물체를 뜻한다. 대부분의 경우, 스프라이트는 비트맵일 뿐

– 8비트 PC인 MSX에서 HW에서 사용된 용어

48

VRAM에 오프스크린 표면 생성

// .. assume DirectDraw has been set up and so on

DDSURFACEDESC2 ddsd; // a DirectDraw surface descriptor

LPDIRECTDRAWSURFACE7 lpwork; // the working surface

// set the size parameter as always

memset(&ddsd,0,sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

// set the flags; very important

// remember that you must set the flags of the fields that will be valid

ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;

// set dimensions of the new surface

ddsd.dwWidth = width;

ddsd.dwHeight = height;

// what kind of offscreen surface, system memory, or VRAM

// default is VRAM

ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

// now create the surface and check for error

if (lpdd->CreateSurface(&ddsd,&lpwork,NULL)!=DD_OK)

{ /* error */ }

49

시스템 메모리에

// set flags for an offscreen plain system memory surface

ddsd.ddsCaps.dwCaps =

DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;

// now create the surface and check for error

if (lpdd->CreateSurface(&ddsd,&lpwork,NULL)!=DD_OK)

{ /* error */ }

50

팔레트…

생략.

51

질문