본문 바로가기
게임 공부/DirectX

Blend state

by woohyeon 2021. 4. 3.
반응형

blend는 사전적으로 혼합이란 의미를 가진다. 컴퓨터 그래픽스에서 블렌딩이라 하면 어떠한 두 픽셀 컬러를 섞는 것을 말한다. 이러한 블렌딩은 주로 어떤 두 픽셀이 겹칠 때 주로 행하게 된다. 예를 들어 물체가 하나 있고 그 앞에 반투명한 빨간 색상의 벽이 있다. 물체는 벽 뒤에 있지만 벽이 반투명하기 때문에 보여야 한다. 이때 화면에 보여질 물체의 색상은 물체의 기존 색상과 동일하진 않을 것이다. 아마 이때의 색상은 벽의 색상 50%, 물체의 색상 50%가 혼합된 색상일 것이다. 

이처럼 투명도를 나타내기 위해 RGB 색상 외에 알파(Alpha)라는 불투명도를 의미하는 값이 존재한다. RGB와 마찬가지로 보통 8비트를 이용하여 0~255 범위의 값을 사용할 수 있지만, 범용성을 위해 정규화된 0~1사이의 값을 사용한다. 예를 들어 16비트를 사용할 경우에도 0~1 사이의 값으로 통일하기 위해..

알파가 1이면 그 물체는 불투명하다. 즉 원색을 그대로 가진다. 알파가 0.5면 반투명, 0이면 완전 투명하다. 이렇게 알파를 포함한 두 색상의 혼합을 나타내는 공식이 존재하는데 다음과 같이 간단하다.

c = srcColor * srcFactor + destColor * destFactor

src는 새롭게 write할 대상, dest는 기존에 존재하던 대상이다. 벽과 물체로 예를 들면, 먼저 그려진 물체가 dest가 되고 새롭게 그릴 벽이 src가 된다. Color는 각 대상의 컬러 값이고 Factor는 알파 값이 된다. 만약 불투명한 물체의 RGBA가  (0, 1, 0, 1)이고, 반투명한 벽의 RGBA가 (1, 0, 0, 0.5)라면 벽을 통해 보이는 물체의 색상은 다음과 같이 구할 수 있다.

c = (1, 0, 0, 0.5) * 0.5 + (0, 1, 0, 1) * (1 - 0.5)

벽은 반투명하므로 기존 색상의 정확히 50% 만큼만 보여야 한다. 따라서 기존 색상에 알파 값 0.5를 곱해준다. 물체의 색상엔 1 - 0.5 = 0.5 즉 50%만큼을 곱해준다. 만약 벽의 알파값이 0.3이라면, 물체엔 1 - 0.3 = 0.7만큼을 곱해준다. 다음 그림은 잔디 사진 위에 반투명한 사각형을 그린 것이다. 알파값이 0에 가까워질 수록 투명해지고, 1에 가까울 수록 불투명해지는 모습을 볼 수 있다.

이러한 동작은 파이프라인의 마지막 단계인 output merging 단계에서 수행된다. 이를 수행하는 output merger에 미리 정의한 blend state를 바인딩함으로서 블렌딩을 수행할 수 있다. 물론 블렌딩은 필수가 아니다. 만약 해당 옵션을 사용하지 않는다면, 픽셀 셰이더가 출력하는 값이 그대로 최종 색상이 된다.

blend state는 ID3D11BlendState 라는 타입을 가진다. Device 객체의 CreateBlendState() 라는 함수를 통해 생성할 수 있다.

HRESULT CreateBlendState(
  const D3D11_BLEND_DESC *pBlendStateDesc,
  ID3D11BlendState       **ppBlendState
);

블렌드 스테이트 객체 생성을 위해선 다음과 같은 D3D11_BLEND_DESC 타입의 객체가 필요하다. 

typedef struct D3D11_BLEND_DESC {
  BOOL                           AlphaToCoverageEnable;
  BOOL                           IndependentBlendEnable;
  D3D11_RENDER_TARGET_BLEND_DESC RenderTarget[8];
} D3D11_BLEND_DESC;

해당 타입 멤버 중 RenderTarget이란 배열을 채워야 하는데, 렌더 타겟은 최대 8개를 가질 수 있기 때문에 desc 또한 최대 8개 받을 수 있도록 배열의 크기가 설정되어 있다. 기본적으로 1개를 사용한다면, RenderTarget[0]에 D3D11_RENDER_TARGET_BLEND_DESC 타입의 객체를 생성하여 넣어주면 된다. IndependentBlendEnable를 false로 설정하면, 렌더타겟을 1개만 사용한다는 것을 의미한다. 즉 [1]~[7]은 무시된다.

D3D11_RENDER_TARGET_BLEND_DESC 타입은 다음과 같다.  

typedef struct D3D11_RENDER_TARGET_BLEND_DESC {
  BOOL           BlendEnable;
  D3D11_BLEND    SrcBlend;
  D3D11_BLEND    DestBlend;
  D3D11_BLEND_OP BlendOp;
  D3D11_BLEND    SrcBlendAlpha;
  D3D11_BLEND    DestBlendAlpha;
  D3D11_BLEND_OP BlendOpAlpha;
  UINT8          RenderTargetWriteMask;
} D3D11_RENDER_TARGET_BLEND_DESC;

SrcBlend, DestBlend, BlendOp 는 한 세트이다. 그리고 SrcBlendAlpha, DestBlendAlpha, BlendOpAlpha 또한 한 세트이다. SrcBlend는 픽셀 셰이더의 출력 RGB 값이다. DestBlend는 현재 렌더 타겟의 RGB 값이다. BlendOp는 Src와 Dest를 어떻게 조합할지를 정의하는 연산을 의미한다. 

SrcBlendAlpha는 픽셀 셰이더의 출력 Alpha 값이다. DestBlendAlpha는 현재 렌더 타겟의 Alpha값이다. BlendOpAlpha는 BlendOp와 같이 두 알파를 어떻게 조합할지 정의하는 연산을 의미한다.

필요 값들을 채운 뒤 blend state 객체를 생성 후 OMSetBlendState 함수를 통해 Output Merger(OM)에 바인딩한다. 그리고 렌더링을 하면 되는 데 주의할 점이 있다. 완전 불투명한 물체들은 깊이에 따른 렌더링 순서가 상관이 없다. 겹치는 부분은 그냥 더 얕은 깊이의 물체의 색상으로 덮어 씌우면 되기 때문에..

하지만 불투명하지 않은 물체들은 단순히 덮어 씌우는 것이 아니라 그 뒤에 있는 물체의 색상을 고려해야 한다. 만약 반투명한 벽을 먼저 그리고 불투명한 물체를 그린다고 생각해보자. 반투명한 벽은 깊이가 0.3이라 가정하자. 물체는 이보다 뒤에 있으므로 0.5정도라 가정하자. 벽을 먼저 그리므로 컬러 버퍼 및 깊이 버퍼엔 벽의 컬러 및 깊이가 저장되어 있다. 이제 물체를 그리려고 보니 겹치는 부분의 깊이가 0.5로 기존의 깊이보다 더 깊다. 즉 더 깊은 부분은 어차피 기존 데이터에 의해 가려지므로 갱신할 필요가 없다고 판단하고 무시한다.

이러한 이유들 때문에, 가장 먼저 불투명한 물체들을 그리고 투명한 물체들은 깊이가 깊은 순서에서 낮은 순서대로 그려야 한다. 

블렌딩 및 컬러 버퍼 깊이 버퍼에 대한 더 자세한 내용은 다음 포스팅을 참고

woo-dev.tistory.com/174?category=915972




댓글