달력

09

« 2018/09 »

  •  
  •  
  •  
  •  
  •  
  •  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  •  
  •  
  •  
  •  
  •  
  •  




알파 블랜딩이랑 버택스(or인덱스)로 만든 하나의 이상의 페이스(플랜등..)에 입힌 텍스쳐에 특정 부분을 투명하게 하기 위한 기능이다.

하지만 알파 블랜딩을 사용할 때 주의점이 존재 하는데,

알파블랜딩을 사용할 때 주는 옵션중에, D3DRS_SRCBLEND, D3DRS_DESTBLEND가 있다.

각각 디폴트 값은, D3DBLEND_ONE, D3DBLEND_ZERO 인데,

사용할 예제에서는

처럼 설정해 주었다.
(추가적인 각 추가적인 각 열거체에 대한 설명은 DX도움말 참조)
저 부분을 셋팅하는 이유는,
이미 뿌려져 있는 텍스쳐와 새로 뿌리는 텍스쳐에 대해서 알파 블랜딩 연산을 위해 사용된다.

일단 2개의 텍스쳐(알파값이 존재하는)를 사용해서 어느정도 겹치게 하여 출력해 보자.

단, 여기서 Front는 Z값이 0.3, Back은 Z값이 0.5 정도로 생각하자.
(즉, Back이 조금더 뒤에 있다고 생각하면 될듯)
먼저, Back를 랜더 후
Front를 랜더링해 보자.

우리가 원하는 정상적인 값이 나왔다.
하지만,
Front를 랜더링 한 후 Back를 랜더링 하게 되면,
원치 않는 결과가 나온다는 것을 알 수 있다.

예상하기론, Back가 더 뒤에 있기 때문에 당연히 Front가 먼저 나와야 하는것은 맞지만,
Front가 알파값이 존재 함에도 불구하고 Back이미지를 가리는 현상을 확인 할 수 있다.
그렇다면 무엇이 문제인가..?

결론만 말하자면, 알파블랜딩을 사용하여 텍스쳐를 출력할 때 기존에 뿌려진 이미지에 대해서 새로 이미지를 뿌리면 기존의 이미지와 Z버퍼, 알파값을 잘 조합하여, 화면에 출력하게 되는데,
Back가 먼저 뿌려졌을 땐 뿌려진 Back이미지에 대해서 Front가 더 낮은 Z버퍼값으로 화면에 출력되면서, 알파값이 존재하는 부분에 대해서,
뒤에 존재하는 Back에 대해서 투명하게 처리를 한다는 것이다.

하지만 Front가 먼저 랜더링 되게 되면, Back가 가려지는 이유는,
알파 블랜딩을 사용하면, Front이미지에 알파값이 존재할 지라도, 그 부분에 대해서도 Z버퍼에 기입을 하게 된다는 것이다.

즉, Front가 뿌려지는 순간 그 부분은 이미 Z버퍼가 0.3으로 변환이 되기 때문에,
아무리 Back이미지를 뿌릴려고 하더라도, Z버퍼의 값이 더 높은 Back이미지는 알파연산이 들어가기도 전에 이미 랜더링 대상에서 제외가 된다는 것이다.


그렇다면 여기서 생각해 볼 수 있는 해결책은,
"그렇다면 무조건 Back이미지를 먼저 랜더링 하게 되면 문제가 없는것 아닌가?"
하는 생각이 들 수 있다.
하지만 추후(파티클이나, 다수의 택스쳐 이미지 사용)에 많은 이미지들을 뿌릴때 모든것을 순서를 생각하면서 랜더링 한다는게,
얼마나 큰 과소비인지를 확인 할 수 있게 된다.

그렇기 때문에, 필요한 부분이 알파테스팅이다.

알파테스팅에 대한 결론부터 내리자면,
알파값에 대해서 Z버퍼에 기입을 하지 않는 다는 것이다.
색상은 Z버퍼에 기입을 하기 때문에 자신보다 뒤에 있는 이미지는 당연히 가려질 것이고.
알파값이 적용된 부분은 Z버퍼에 기입을 하지 않았기 때문에 자신보다 뒤에 있는 이미지라고 할지라도 랜더대상에 포함이 된다는 것이다.

사용은 다음과 같다.


각각에 파라미터에 대한 설명을 하자면,
D3DRS_ALPHATESTENABLE의 디폴트 값은 FALSE로써, 단순히 TRUE를 사용해 사용여부를 확인한다.
D3DRS_ALPHAREF와 0x00000088는 알파테스팅중 사용하게 될 알파값의 수치이다.
이미지가 가지고 있는 알파값과 2번째 파라미터에 대해서 연산을 통해 Z버퍼에 기입을 할건지, 말건지(or 랜더링 할지 말지)를 선택하게 된다는 것이다.
마지막으로
D3DRS_ALPHAFUNC는 D3DCMP_GREATER를 사용해 이미지의 알파값이 ALPHAREF의 2번째 파라미터와 계산했을때, 더 큰지, 작은지, 같은지, 또는 같지 않은지 등에 대해 검사를 하게 되는 것이다.
추가로 D3DRS_ALPHAREF의 디폴트 값은 0
D3DRS_ALPHAFUNC의 디폴트 값은 FALSE이다.

이제 알파테스팅을 사용하고,
Front를 먼저 출력해 주어도.
우리가 원하는 결과가 나오는 것을 확인 할 수 있다.

그렇게 따지면 알파테스팅만 쓰지, 알파 블랜딩은 왜 쓰느냐?
라고 생각할 지도 모르겠지만, 알파 테스팅은 치명적인 결점이 있다.
바로 그것은 이미지의 알파값에 대해서 연산후, 그 연산이 참이냐, 거짓이냐에 따라,
무조건, 투명 or 불투명으로 단정지어지게 된다는 것이다.
즉, 자연스러운 알파값의 변환이 불가능하다고 보면된다.

이미지 마다, 낮은 알파, 높은 알파값등을 사용해 자연스러운 효과를 내고 싶어도 그럴 수 없다는 것이다.
오로지 검사를 통해 투명, 불투명으로만 나뉘게 되기 때문에,
어떤 상황에 적절하게 사용할지는 역시 프로그래머에게 달려 있는 듯 하다.
Posted by 안식의후크
2010.06.14 11:38

DirectX - 행렬(Matrices) 프로그래밍/DirectX2010.06.14 11:38





Dx에서 3차원의 정점(Vertex)를 화면상의 모니터로 출력해 주기 위해서는

3가지의 행렬 변환이 필요 하다.

1. 월드 행렬
2. 뷰 행렬
3. 투영 행렬

월드 행렬의 경우에는 3D오브젝트의 로컬좌표를 3차원의 필드에 특정 위치에 뿌려 주기 위해서,
( 좀더 간단하게 말하면, 로컬 좌표를 월드 좌표로 변환 하는 작업 )

뷰 행렬은 카메라를 셋팅한다고 생각하면 되는데,
뷰 행렬을 만들기 위해서는 추가적인 3개의 백터가 필요 하다.
카메라의 위치, 카메라가 바라보는 방향, 카메라의 Up벡터가 필요 하다.

마지막으로 투영 행렬은,
각각의 3D좌표들을 우리의 눈으로 보게 되는 2D로 변경하는 작업을 한다.

이런 작업을 렌더링 파이프라인이라고 하는데,

3D의 좌표를 모니터 화면에 뿌려주기 위한 작업이라고 보면 된다.


이제 이 작업을 Render할때 마다 해주어야 한다.
D3DInit나 InitVB와는 다르게,

행렬에 관한 연산은 매번 처리를 해 주어야 한다.

전체 소스는 생략,

Posted by 안식의후크





먼저 정점을 띄우기 위해서는 정점을 보관할 정점 버퍼가 필요 합니다.

정점 버퍼에 FVF(사용자 정의 정점 형식)를 사용하여 생성할 수 있습니다.

최초 생성한 정점 버퍼는 쓰레기 값이 들어가 있기 때문에,

Lock함수를 사용하여 값을 넣을 수 있는 포인터를 가져 옵니다.

그 후 미리 지정해 놓은 정점 정보를 정점 버퍼에 저장합니다.


바로 그 일을 하는 함수가 InitVB입니다.

이제 정점 버퍼를 생성하고 값을 대입하였으니,

화면에 띄워 주는 일만 남았습니다.

화면에 띄워 주기 위해선 정점 버퍼를 출력 스트림에 할당하고,
셰이더 정보를 삽입한 후에,
화면에 출력시켜 주면됩니다.

이게 전반적인 흐름이고,
조금 자세히 한번 살펴 보도록 합시다.

CreateVertexBuffer 함수는 정점 버퍼를 생성하는 함수인데,
UINT Length,( 생성할 정점 버퍼의 바이트 단위 크기 )
DWORD Usage,( 정점 버퍼의 종류 혹은 처리 방식(SW, HW) 지정 )
DWORD FVF, ( 정점 정보 구조체에 따라 선언된 FVF 플래그 값 )
D3DPOOL Pool, ( 정점 버퍼가 저장될 메모리의 위치( 비디오카드, 시스템 메로리) 와 관련 방식 지정 )
IDirect3DVertexBuffer9** ppVertexBuffer, ( 반환될 정점 버퍼의 인터페이스 )
HANDLE* pSharedHandle
로 이루어져 있는데 HANDLE의 경우는 NULL을 넣어 주는것 같습니다.

다음으로 Lock 함수 같은 경우에는,
UINT OffsetToLock, ( Lock을 할 버퍼의 시작점, SizeToLock과 함께 양쪽 모두 0이면 버퍼 전체 )
UINT SizeToLock, ( Lock을 할 버퍼의 크기, OffsetToLock과 함께 양쪽 모두 0이면 버퍼 전체 )
void** ppbData, ( 읽고 쓸 수 있게 된 메모리 영역의 포인터 )
DWORD Flags ( Lock을 수행할 때 함께 사용하는 플래그 )
예제의 사용을 보면 버퍼의 시작점은 0으로 두고
사이즈는 sizeof를 사용해 할당 받고,
VOID* pVertices 라는 변수에 메모리 영역의 포인터를 삽입,
플래그는 주지 않은 상태인것을 확인 할 수 있습니다.

Lock함수의 주의점은 꼭 마지막에 UnLock를 해주어야 한다는 것을 잊지 맙시다.

다음으로 Render를 해줄때 사용하는
SetStreamSource, SetFVF, DrawPrimitive 함수를 살펴 봅시다.

책에선 SDK도움말을 확인 하라길래 그냥 인터넷에서 뒤져서 대충 정리해 보았습니다.

SetStreamSource 함수,
UINT StreamNumber, ( 스트림의 최대숫자까지 설정 할 수 있다는데, 보통 0으로 한다고 함 )
IDirect3DVertexBuffer9* pStreamData, ( 정점 버퍼가 저장된 데이타 )
UINT OffsetInBytes, (정점 버퍼에 시작될 스트림 오프셋 값, 보통 0을 지정한다.)
UINT Stride ( 한 정점의 크기로 앞서 설정된 구조체 크기를 넣어 준다.)

SetFVF 함수,
DWORD FVF ( 유연 정점 포맷, FVF의 설정 )

DrawPrimitive 함수,
D3DPRIMITIVETYPE PrimitiveType, ( 어떤 방식으로 그려줄 것인가에 대한 속성 값 )
UINT StartVertex, ( 로드하는 최초 정점 인덱스 )
UINT PrimitiveCount ( 랜더링 하는 도형의 갯수 )

대충 이정도 개념을 잡고 있으면 정점에 대해선 어느정도 이해한거라고 생각합니다.

// 전체 소스


// 실행 화면
Posted by 안식의후크





디바이스를 생성할때 InitD3D함수에서.


다음과 같은 부분이 있었습니다.
전 그냥 Windowed 라는 부분을 FALSE로 해주면
전체화면이 되겠거니, 하였으나,
그렇지 않더군요.

단순히 전체화면을 하고 싶을 뿐인데, 생각보다 해야하는 작업이 많았습니다.
(그렇다고 어렵진 않았습니다.)

일단 Windowed 속성을 FALSE로 주어야 하는건 맞지만 저것만 가지고는 부족하다는 겁니다.

요점만 먼저 말하자면,
D3DPRESENT_PARAMETERS 구조체에 몇가지 정도를 더 지정해 주어야 하며,
WinMain 함수에서 CreateWindow 또한 약간 손보아야 합니다.

먼저 예제의 실행 화면입니다.

자 그럼 이제 이 화면을 전체화면으로 바꾸어 봅시다.

먼저 CreateWindow 함수를 다음처럼 변경합니다.

어느 부분이 바뀌었는지는 다들 감을 잡으셨을 겁니다. WS_EX_TOPMOST | WS_POPUP 이부분입니다.
당연히 시작 위치는 0, 0
사이즈는 800 x 600 으로 잡아 주었네요.
이렇게만 하고 실행을 해보면 실행에는 문제가 없습니다.

여전히 파란 화면 뿐이지만, 테두리같은것이 다 사라지고 단순히 퍼런 창만 보입니다.

하지만 아직 전체화면은 되지 않았습니다.

자 일단 윈도우에 테두리를 지우고 어느정도 사이즈를 잡아 주었으니,
다음으로 해야 할 일은, 현재 해상도를 저 크기에 맞게 조절해야 한다는 것인데.
InitD3D 함수로 이동해서
D3DPRESENT_PARAMETERS 구조체의 설정을 할 때 다음처럼 변경해 봅시다.

간략하게 설명을 드리자면, 바뀐 부분은 Windowed 의 값이 TRUE에서 FALSE로 바뀌었습니다.(당연한 이치)

BackBufferFormat 에 값이 D3DFMT_X8R8G8B8로 변경되었습니다.
D3DFMT_UNKNOWN의 속성을 그대로 두면 되지 않는다고 합니다.
저 부분은 현재 윈도우에 대해서 32비트인지 16비트인지 하는 옵션을 조정하는 부분입니다만,
잘 모르시겠으면 그냥 저대로 하셔도 큰 문제는 없을 것 같네요. 그리고 SwapEffect는 변경되지 않았고,
hDeviceWindow, BackBufferWidth, BackBufferHeight에 대해서 추가되었습니다.

hDeviceWindow 에 대해서는 현재 윈도우 핸들을 넘겨주면 되고,


BackBufferWidth, BackBufferHeight는 딱 보면 아시겠지만 모니터의 해상도를 변경하는 부분으로 예상됩니다.

다음 처럼 코드를 변경하신 후 실행하면 해상도가 변경되는 느낌(?)이 들면서 전체화면으로 보여지는 파란 화면이 보이실 겁니다.

혹시 전체화면에 대해서 어떻게 하시는지 궁금해 하실 분이 있을 것 같아 미리 찾아보았습니다.
Posted by 안식의후크
2010.05.17 03:44

DirectX-디바이스 생성 프로그래밍/DirectX2010.05.17 03:44






자 이제 DX를 해봅시다!
일단 닥치고 코드를 칩니다.

실행하면 파란 화면이 나옵니다!
끝!!!!!!!!!!!!!

이라고 말하고 싶지만, 한번 천천히 살펴 봅시다;

API에 WindowEx를 만드는 소스를 참고하시면 이해가 편합니다.

일단 순서를 살펴봅시다. 140줄이나 되지만 고작 파란 화면만 뜨더니 끝나는,

기본적인 API의 메시지큐 구조를 파악했다는 전제로 저희가 알아야할 부분은
InitD3D 함수와,
Rander, Cleanup 함수 입니다.
변수로는 전역에 선언된
LPDIRECT3D9
LPDIRECT3DDEVICE9
에 대해서 어느정도 파악을 해보도록 합시다.

프로그램을 실행하면
최초 INT WINAPI WinMain 함수에 들어가게 됩니다.
기존 API처럼 WndClass를 등록하고 화면에 띄우는작업은 거의 비슷하지만,
메시지 루프에 들어가기전 InitD3D라는 함수 들르는것이 보입니다.

모든 내용을 다 알필요는 없다고 생각됩니다. 적당히 그런가? 정도로 파악하고 넘어갑시다.
언젠간 확실히 알게 될....꺼라 생각합니다.
코드 설명은 주석에 어느정도 달려 있으니 그냥 그런가 하고 넘어간다고 치고.
InitD3D에서 하는 일은,
D3D객체를 만들고,
디바이스에 대한 설정을 한 이후에,
그 설정값을 가지고, 디바이스를 생성!
하는 것 같습니다.
아마 모든 작업이 무사히 완료 됐다면 이제 메시지 루프에 들어가게 되겠지요.

메시지 루프에 들어가보니 WM_PAINT에서
Render 함수를 호출하네요,
바로 밑에는 ValidateRect를 해주는것을 보아하니,
지속적으로 WM_PAINT를 돌려 주는것 같습니다.
Render함수에서 이제 무언가 그려줘야 겠지만
이번 예제는 아무것도 그려주지 않습니다.
Render함수에서 하는 일은,
일단 g_pd3dDevice 의 변수가 아직 유효한가에 대해서 검사하는것 같습니다.
혹시라도 디바이스가 지워지지(NULL값이)않았는가 하는 예외처리를 해주고 있네요.
후면 버퍼를 퍼렇게 물들이고,
BeginScene과 EndScene 사이에 무언가를 렌더링 한 후에,
Present를 사용해서 후면 버퍼에 있는 디바이스를 보여주도록 이루어져 있습니다.
프로그램이 실행되는 동안에는 무한의 Rander함수 호출과,
이벤트에 따른 동작들이 이루어 지는것 같습니다.
이제 프로그램을 종료 하게 되면,
WM_DESTORY로 메시지가 들어가게 될테고

PostQuitMessage를 하기전에 Cleanup 함수를 호출합니다.


Cleanup 함수에 가보니, 전역으로 잡아 주었던 D3D객체와 디바이스를 NULL값으로 주고 있네요.
그 후 릴리즈를 사용해서 완벽하게 지워줍니다.

어차피 당장 무언가 거대한걸 바라는것도 아니니,
일단 이정도로만 개념을 잡아두면 당분간은 무리가 없을 것 같습니다.
Posted by 안식의후크