리듬게임을 만들어보자 3. 다양한 그리기 효과

Posted by 적분 ∫2tdt=t²+c
2009.11.22 22:41 프로그래밍/스페샬

MidiGalaxy-03.zip

멋진 그래픽을 만드려고 노력하다 보면 알파블렌딩만으로는 원하는 걸 만들지 못할 때가 있다.
그래서 다양한 그래픽 효과를 여기서 추가해보고자 한다.

D3D에서는 Render State라고 해서 렌더링 시에 여러가지 설정을 줄수 있게 해놓았다.
IDirect3DDevice9::SetRenderState로 RenderState를 설정할 수 있고
IDirect3DDevice9::GetRenderState로 RenderState값을 가져올 수 있다.

다음과 같이 쓰면 된다.
SetRenderState( 상태 종류, 설정할 값 );
예를 들자면
m_pd3dd->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS);
이런 식으로 하면 된다.

여기서는 특히 중요한 몇 개의 설정들을 살펴보겠다.

*D3DRS_ALPHAFUNC
알파채널이 섞인 텍스쳐를 화면에 출력할 때 어떻게 할 것인가?

1. D3DCMP_NEVER : 절대로 출력하지 않는다.
2. D3DCMP_LESS : D3DRS_ALPHAREF 값보다 작으면 출력한다.
3. D3DCMP_EQUAL : D3DRS_ALPHAREF 값과 같으면 출력한다.
4. D3DCMP_LESSEQUAL : D3DRS_ALPHAREF 값과 같거나 작으면 출력한다.
5. D3DCMP_GREATER : D3DRS_ALPHAREF 값보다 크면 출력한다.
6. D3DCMP_NOTEQUAL : D3DRS_ALPHAREF 값과 같지 않으면 출력한다.
7. D3DCMP_GREATEREQUAL : D3DRS_ALPHAREF 값과 같거나 크면 출력한다.
8. D3DCMP_ALWAYS : 항상 출력한다.


*D3DRS_ALPHAREF
D3DRS_ALPHAFUNC에서 비교대상으로 사용할 값

예를 들어서 알파채널이 100 이상인 부분만 출력하고 싶다면,

SetRenderState(D3DRS_ALPHAREF, 100);
SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
이렇게 하면 된다. 참 쉽죠?

*D3DRS_BLENDOP
텍스쳐가 그려질 때, Dest과 Src를 어떻게 섞을까?
(Dest는 그려질 자리에 원래 있던 그림, Src는 새로 그려질 그림을 말합니다.)

1. D3DBLENDOP_ADD :  Result = Dest + Src
2. D3DBLENDOP_SUBTRACT :  Result = Src - Dest
3. D3DBLENDOP_REVSUBTRACT :  Result = Dest - Src
4. D3DBLENDOP_MIN :  Result = Dest, Src 중 작은 값
5. D3DBLENDOP_MAX :  Result = Dest, Src 중 큰 값


*D3DRS_SRCBLEND, D3DRS_DESTBLEND
Dest과 Src를 섞을때 어떤 값을 곱할까?

1. D3DBLEND_ZERO : 0을 곱한다.
2. D3DBLEND_ONE : 1을 곱한다.
3. D3DBLEND_SRCCOLOR : Src의 R, G, B, A값을 곱한다.
4. D3DBLEND_INVSRCCOLOR : Src의 1-R, 1-G, 1-B, 1-A값을 곱한다.
5. D3DBLEND_SRCALPHA : Src의 Alpha값을 곱한다.
6. D3DBLEND_INVSRCALPHA : Src의 1-Alpha값을 곱한다.
7. D3DBLEND_DESTALPHA : Dest의 Alpha값을 곱한다.
8. D3DBLEND_INVDESTALPHA : Dest의 1-Alpha값을 곱한다.
9. D3DBLEND_DESTCOLOR : Dest의 R, G, B, A값을 곱한다.
10. D3DBLEND_INVDESTCOLOR : Dest의 1-R, 1-G, 1-B, 1-A값을 곱한다.

이 얘기로 충분히 이해가 안 될지도 모른다.
그래서 예를 들어가면서 설명하겠다.

*알파블렌딩을 하고 싶다. 즉 Src의 알파값이 255면 Src가 그대로 나타나고, 127 이면 Src와 Dest가 반반씩 섞여서 나오고, 0 이면 Dest가 그대로 나오게 하고 싶으면?

SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

그러면 Src에 (알파값)이 곱해지고 Dest에 (1-알파값)이 곱해지고 둘을 더한 것이 결과가 된다.
Result= Src * SrcAlpha + Dest * (1 - SrcAlpha)
그러면 Src의 알파값이 클수록 Src가 진하게 나타나고, 작을수록 Dest가 진하게 나타날것이다.


*블렌딩이고 뭐고 없이 Src를 그대로 화면에 출력하고 싶으면?

SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

Result = Src * 1 + Dest * 0 이 되어서 결국 Result = Src 가 된다.
Src의 알파값이 어떻든 항상 Src가 그대로 출력되는것이다.


*Dest에 Src를 더해지게 하고 싶으면?

SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

Result = Src * 1 + Dest * 1

Src의 알파값이 반영되게 하고 싶으면
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
Result = Src * SrcAlpha + Dest * 1 이 되어, 알파값 반영된다.


*Dest에 Src를 마스킹하고 싶으면?
SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
Result = Src * 0 + Src * DestColor =  Src*Dest

기타 등등등... 응용방안은 무궁무진하다. 게다가 ID3DXSprite::Draw에 넣어주는 Color값이 기억나는가? 그것까지 이용한다면 더 강력한 그래픽 효과를 사용할수 있을것이다.

Result = Dest * DestBlend + Src * SrcBlend * Color (BlendOp에 따라서 +가 - 나 min, max로 바뀔 수 있는 것도 기억!)

참고:

*D3DRS_ALPHAREF 를 0으로 설정하고 D3DRS_ALPHAFUNC 를 D3DCMP_GREATER로 설정하면
알파채널이 0보다 큰 부분만 출력된다. 근데 D3DRS_ALPHAFUNC 를 D3DCMP_ALWAYS 로 설정해도 어차피 알파채널이 0인 부분은 그리면 출력안되니깐 둘은 같지 않은가?

같지 않다! AlphaFunc에서 걸러진 픽셀들은 SrcBlend, DestBlend에 와보지도 못하지만, AlphaFunc을 통과한 픽셀은 SrcBlend, DestBlend 단계까지 올 수 있다.

예)
SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
일때

i)
SetRenderState(D3DRS_ALPHAREF, 0);
SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);

ii)
SetRenderState(D3DALPHAFUNC, D3DRS_ALWAYS);

i)와 ii)경우를 생각해보자.

i)는 알파값이 0인 픽셀들을 걸러져서 그려지지 않으므로, 알파가 0인 부분은 Dest가 그대로 남아있을 것이다.
ii)는 알파값이 0인 픽셀들이 AlphaTest를 통과하고 Result=Dest*0 + Src*1 이므로, 알파값이 0인 부분도 Src가 되어있을것이다.

그리기 모드를 설정하는 함수를 추가해보자. RenderState를 설정하여 무궁무진한 그래픽 효과를 줄수 있지만, 그중에서 주로 쓰이는 몇 가지만 골라봤다.


SetRenderState로 상태를 변경하기 전에 먼저 ID3DXSprite::Flush()를 호출했다. 이 함수는 그동안 그려놓았던 것들을 비워주는 함수이다. ID3DXSprite는 Draw하면 그때 바로 화면에 그리는 게 아니라, 차곡차곡 모아두었다가 한 번에 화면에 그려준다. 만약에 전에 그리라고 했던것들이 화면에 그려지지 않고 ID3DXSprite내에 쌓여있었다면, RenderState를 바꿔주면, 쌓여있던 것들에게까지 전부 영향을 주게 된다. 그러므로 RenderState를 바꾸기 전에는 먼저 Flush()를 해줘야 원하는 결과를 얻을수 있다.

SetRenderState(D3DRS_COLORWRITEENABLE, cmode)는 그릴 채널을 선택하는 부분이다. 때때로, 텍스쳐의 Red 채널만 그리고 싶다던가, Blue 채널만 그리고 싶다던가 할 때가 있다. 이 때, D3DRS_COLORWRITEENABLE을 변경함으로써 그 소원을 이룰 수 있다.

이제 한 번 테스트를 해보자.


이 댓글을 비밀 댓글로
  1. 잘 보고 갑니다^^
    • 웃음
    • 2016.03.17 18:16


    *D3DRS_ALPHAFUNC
    알파채널이 섞인 텍스쳐를 화면에 출력할 때 어떻게 할 것인가?

    1. D3DCMP_NEVER : 절대로 출력하지 않는다.
    2. D3DCMP_LESS : D3DRS_ALPHAREF 값보다 작으면 출력한다.
    3. D3DCMP_EQUAL : D3DRS_ALPHAREF 값과 같으면 출력한다.
    4. D3DCMP_LESSEQUAL : D3DRS_ALPHAREF 값과 같거나 작으면 출력한다.
    5. D3DCMP_GREATER : D3DRS_ALPHAREF 값보다 크면 출력한다.
    6. D3DCMP_NOTEQUAL : D3DRS_ALPHAREF 값과 같지 않으면 출력한다.
    7. D3DCMP_GREATEREQUAL : D3DRS_ALPHAREF 값과 같거나 작으면 출력한다.
    8. D3DCMP_ALWAYS : 항상 출력한다.


    에서
    4번과 7번의 설명이 같습니다 오타이신가요?