
멋진 그래픽을 만드려고 노력하다 보면 알파블렌딩만으로는 원하는 걸 만들지 못할 때가 있다.
그래서 다양한 그래픽 효과를 여기서 추가해보고자 한다.
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를 설정하여 무궁무진한 그래픽 효과를 줄수 있지만, 그중에서 주로 쓰이는 몇 가지만 골라봤다.
GRET GGame::SetDrawMode(int mode, int cmode)
{
if(!m_pd3dd)return GRET_ERROR_NODEVICE;
m_pspr->Flush();
m_pd3dd->SetRenderState(D3DRS_COLORWRITEENABLE, cmode);
switch(mode)
{
case DM_Normal: // Result = Src*SrcAlpha + Dest*(1-SrcAlpha)
m_pd3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pd3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
m_pd3dd->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
break;
case DM_NoBlend: // Result = Src*1 + Dest*0
m_pd3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
m_pd3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
m_pd3dd->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
break;
case DM_Add: // Result = Src*SrcAlpha + Dest*1
m_pd3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pd3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
m_pd3dd->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
break;
case DM_Subtract: // Result = Dest*1 - Src*SrcAlpha
m_pd3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
m_pd3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
m_pd3dd->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_REVSUBTRACT);
break;
case DM_Multiply: // Result = Src*DestColor + Dest*0
m_pd3dd->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
m_pd3dd->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
m_pd3dd->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
break;
}
return 0;
}
SetRenderState로 상태를 변경하기 전에 먼저 ID3DXSprite::Flush()를 호출했다. 이 함수는 그동안그려놓았던 것들을 비워주는 함수이다. ID3DXSprite는 Draw하면 그때 바로 화면에 그리는 게 아니라, 차곡차곡모아두었다가 한 번에 화면에 그려준다. 만약에 전에 그리라고 했던것들이 화면에 그려지지 않고 ID3DXSprite내에쌓여있었다면, RenderState를 바꿔주면, 쌓여있던 것들에게까지 전부 영향을 주게 된다. 그러므로 RenderState를바꾸기 전에는 먼저 Flush()를 해줘야 원하는 결과를 얻을수 있다.
SetRenderState(D3DRS_COLORWRITEENABLE, cmode)는 그릴 채널을 선택하는 부분이다. 때때로,텍스쳐의 Red 채널만 그리고 싶다던가, Blue 채널만 그리고 싶다던가 할 때가 있다. 이 때,D3DRS_COLORWRITEENABLE을 변경함으로써 그 소원을 이룰 수 있다.
이제 한 번 테스트를 해보자.
void GGame::OnDraw()
{
SetDrawMode(DM_Normal);
m_sprs.DrawSprite(0, 10, 10, D3DCOLOR_ARGB(255, 255, 255, 255));
SetDrawMode(DM_Add);
m_sprs.DrawSprite(0, 140, 10, D3DCOLOR_ARGB(255, 255, 255, 255));
SetDrawMode(DM_Subtract);
m_sprs.DrawSprite(0, 270, 10, D3DCOLOR_ARGB(255, 255, 255, 255));
SetDrawMode(DM_Multiply);
m_sprs.DrawSprite(0, 400, 10, D3DCOLOR_ARGB(255, 255, 255, 255));
SetDrawMode(DM_NoBlend);
m_sprs.DrawSprite(0, 530, 10, D3DCOLOR_ARGB(255, 255, 255, 255));
}
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.