DirectX

7. RasterizerState, SampleState, BlendState

devRiripong 2023. 12. 14.
반응형

이번 시간에 한 것들

 

1. RasterizerState

    1) Comptr로 RaterizerState 선언
    2) CreateRasterizerState구현, RASTERIZER_DESC의 세팅 후 _device->CreateRasterizerState의 인자로 넣어 _rasterizerState 내용 채우기, Init에서 CreateRasterizerState 호출하기
    3) Render의 RS에서 RSSetState로 _rasterizerState 세팅해주기
    4) CreateRasterizerState에서 desc 세팅 바꿔주고 실험1_WIREFRAME
    5) CreateRasterizerState에서 desc 세팅 바꿔주고 실험2_CULL

 

2. SampleState

    1) ComPtr로 SamplerState 선언
    2) CreateSamplerState에서 DESC 세팅 후 _device->CreateSamplerState인자로 넣어 _samplerState 내용 채우기, Init에서 CreateSamplerState 호출하기
    3) Render의 PS에서 RSSetSamplers로 _samplerState 세팅해주기
    4) CreateSamplerState에서 desc 세팅 바꿔주고 실험_MIRROR

 

3. BlendState

    1) ComPtr로 BlendState선언
    2) CreateBlendState에서 DESC 세팅 후 _device->CreateBlendState인자로 넣어 _blendState 내용 채우기, Init에서 CreateBlendState 호출하기
    3) Render의 OM에서 OMSetBlendState로 _blendState 세팅 해주기

 

 

비교적 중요하지 않은 삼총사에 대해 알아볼 것이다.

1. RasterizerState

Render로 가서 보면 RS부분이 비어 있다. 

Rasterize단계에서 코딩은 할 수 없지만 설정은 할 수 있다고 했어. 
그걸 해보려고 한다.

Rasterize는 VS가 넘겨준 삼각형들을 대상으로 삼각형 내부에 있는 모든 픽셀들을 판별하는 그 영역이라고 했는데, 

1) Comptr로 RaterizerState 선언

Game.h에 선언해 준다.

	// RAS
	ComPtr<ID3D11RasterizerState> _rasterizerState = nullptr;

 

참고로 자동 완성될 때 ID3D11RasterizerState1, ID3D11RasterizerState2 이런식으로 여러가지 버전이 있을 수 있는데, 
지금 작업하는게 DirectX11.0 최초 버전을 얘기하는 것이고, 여기가 11.1, 11.2 올라가면서 상위 호환 애들이 나타났다. 이거에 따라서 DX11 라이브러리 버전을 바꿔줄 필요가 있다. 호환성 측면에서는 11.0 버전으로 해도 상관 없어서 이걸로 한다.

 

2) CreateRasterizerState구현, RASTERIZER_DESC의 세팅 후 _device->CreateRasterizerState의 인자로 넣어 _rasterizerState 내용 채우기, Init에서 CreateRasterizerState 호출하기

_raterizerState를 만들어 보고 고쳐 볼 것인데, 
일단CreateRasterizerState로 만들어 보도록 할 것이다. 

함수를 Game.h에 선언하고, 구현을 한다.

	void CreateRasterizerState();
void Game::CreateRasterizerState()
{
	D3D11_RASTERIZER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc)); 
	desc.FillMode = D3D11_FILL_SOLID; 
	desc.CullMode = D3D11_CULL_BACK; 
	desc.FrontCounterClockwise = false; // 이것들은 실습을 하면서 얘기를 해볼거야. 

	HRESULT hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
	CHECK(hr); 
}

이렇게 RasterizerState를 Create하기로 했으면, Init에 가서, 원래라면 CraetePS(); 전에 호출해 줘야 겠지만, 그 다음에 호출해 주도록 한다. ( DirectX 렌더링 파이프라인에서 각각의 구성 요소가 독립적으로 생성되고 설정될 수 있다. 즉, 이들의 생성 순서는 일반적으로 파이프라인의 실행 순서에 엄격하게 의존하지 않는다.)

 

3) Render의 RS에서 RSSetState로 _rasterizerState 세팅 해주기

CreateRasterizerState를 호출해서 _rasterizerState에 내용이 들어갔으면, 
똑같이 Rendering을 할 때 어딘가에다가 Binding을 해주면 된다. 

Render의 RS에다가

	// RS
	_deviceContext->RSSetState(_rasterizerState.Get());

지금까지 한 것 빌드 해서 실행해 보면 딱히 바뀐 게 없다. 

일단 이전 시간에 그림이 움직이게 했던 걸 멈추게 한다.

void Game::Update()
{
	//_transformData.offset.x += 0.003f; 
	//_transformData.offset.y += 0.003f;

 

4) CreateRasterizerState에서 desc 세팅 바꿔주고 실험1_WIREFRAME

Rasterizer와 관련된 부분들을 몇 개를 건드려 볼 것인데, 
void CreateRasterizerState()에 가서 보면

void Game::CreateRasterizerState()
{
	D3D11_RASTERIZER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc)); 
	desc.FillMode = D3D11_FILL_SOLID; 
	desc.CullMode = D3D11_CULL_BACK; 
	desc.FrontCounterClockwise = false; // 이것들은 실습을 하면서 얘기를 해볼거야. 

	HRESULT hr = _device->CreateRasterizerState(&desc, _rasterizerState.GetAddressOf());
	CHECK(hr); 
}

FillMode가 Fill_SOLID라고 되어 있다. 
F12로 가서 보면 

typedef 
enum D3D11_FILL_MODE
    {
        D3D11_FILL_WIREFRAME	= 2,
        D3D11_FILL_SOLID	= 3
    } 	D3D11_FILL_MODE;

FILL_WIREFRAME이라는 게 하나 더 있는 걸 볼 수 있다. 
FILL_WIREFRAME얘로 바꿔주면 

뭔가 이상하게 삼각형 안이 채워지지 않게 나온다. 
유니티에서도 옵션을 WIREFRAME으로 바꾸면 삼각형들만 보이는데 그게 와이어 프레임이다 .
다시 FILL_SOLID로 바꿔 놓자. 

 

5) CreateRasterizerState에서 desc 세팅 바꿔주고 실험2_CULL

그리고 CullMode가 D3D11_CULL_BACK으로 되어 있는데, 
Back이면 Culling을 하겠다고 되어 있는데 
Culling이란 그리지 않고 스킵하는 걸 컬링이라고 한다. 카메라 영역에서 벗어나면 Culling을 하거나, 안보이는 물체를 보여서 낭비 할 필요가 없을 때, 여기서는 후면으로 인식을 하면  그리지 않겠다 라고 해서 설정이 되어 있다. 
뒤라는 걸 어떻게 인식을 하느냐, 일단 앞을 인식하는지 보자 
만약 cullMode를 D3D11_CULL_FRONT로 바꾼다면, 

실행해 보면 아무것도 안그려 지는 것을 볼 수 있다.
다시 BACK으로 바꿔준다.

 

앞뒤는 어떻게 판별하는 것이냐? 

desc.FrontCounterClockwise = false;

이 옵션에 따라가지고 판별한다. 앞방향은 반시계 방향입니까? false
시계방향으로 가는게 앞방향이 된다. 
Index buffer에서도 삼각형을 만들 떄 시계 방향으로 정점의 순서를 적었었다. 그렇게 되면 앞면이 되는 거. 카메라 기준에 따라. 카메라가 반대에 있으면 반시계 방향이 되니, 후면으로 인식해서 안그리게 된다. 물체에 따라 앞만 보여야 되고 뒤에서 보면 안보이게 해줘야하면 이 옵션에 따라 culling을 해줘서 뒷면은 안보이게 해줄 수 있다. 그러면 굳이 안보이는 건 안그리니 성능향상을 얻을 수 있다. 이런 측면에서도 rasterizer를 사용할 수 있다고 보면 된다. 

 

    typedef struct D3D11_RASTERIZER_DESC
    {
    D3D11_FILL_MODE FillMode;
    D3D11_CULL_MODE CullMode;
    BOOL FrontCounterClockwise;
    INT DepthBias;
    FLOAT DepthBiasClamp;
    FLOAT SlopeScaledDepthBias;
    BOOL DepthClipEnable;
    BOOL ScissorEnable;
    BOOL MultisampleEnable;
    BOOL AntialiasedLineEnable;
    } 	D3D11_RASTERIZER_DESC;

이 밖에도 기능들이 많으니 궁금한 거 찾아보면 된다. 

 

CraeteGeometry에서 UV값을 2에서 1로 다시 세팅을 해준다.

 

덜 중요하고 구현 않고 실행했을 때 문제가 없었던 3총사 중에서 RasterizerState를 사용해 보았다. 

 

나머지도 이런식으로 사용하면 된다.
새로운 기능을 넣어야 된다 싶을 떄 문서를 찾아봐서 렌더링 파이프라인에서 어느 단계에 들어가는지 보고 그거에 따라가지고 맞춰주면 된다. 

2. SampleState

두번째로 알아 봐야 할 것은 SampleState다. 
Game.h 에 

void CreateSamplerState(); 
void CreateBlendState();

를 선언해 준다.. 
구현부를 생성해 주고, Init에 간 다음에 CreateRasterizeState(); 다음에 호출한다.

 

CreateSamplerState부터 구현한다.

언제 설정하는 거였는지 보면 

Texture2D texture0 : register(t0); 
Texture2D texture1 : register(t1);
SamplerState sampler0 : register(s0); 

float4 PS(VS_OUTPUT input) : SV_Target
{      
    float4 color = texture0.Sample(sampler0, input.uv);
    return color; 
}

 

SamplerState 객체 sampler0를 선언하고 있으며, 이 객체는 register(s0)에 할당된다.

texture0.Sample(sampler0, input.uv)sampler0에 정의된 샘플링 설정을 사용하여 texture0에서 특정 UV 좌표(input.uv)에 해당하는 색상을 샘플링합니다. 이렇게 샘플링된 색상(color)은 픽셀 셰이더의 출력으로 사용되며, 최종적으로 픽셀의 색상을 결정합니다.

텍스처 샘플링은 텍스처 이미지(2D 이미지 데이터)에서 특정 위치의 색상 값을 가져오는 과정을 말한다.

 

texture0의 Sample함수에 uv좌표랑 smapler0을 꺼내준다. 그러면 어떠한 색상인지를 꺼내줘서 색상을 결정한다. sampler는 그거에 관한 옵션이라고 일단은 생각하면 된다. 

 

1) ComPtr로 SamplerState 선언

Game.h.에 

ComPtr<ID3D11SamplerState> _samplerState = nullptr;

선언하고,

 

2) CreateSamplerState에서 DESC 세팅 후 _device->CreateSamplerState인자로 넣어 _samplerState 내용 채우기, Init에서 CreateSamplerState 호출하기

void Game::CreateSamplerState()
{
	D3D11_SAMPLER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc));
	// 여기다가 마음에 드는 옵션들을 세팅해 보면된다. 

	_device->CreateSamplerState(&desc, _samplerState.GetAddressOf()); 
}

_samplerState에 값이 채워진다.

 

D3D11_SAMPLER_DESC를 f12로 살펴보면, 

typedef struct D3D11_SAMPLER_DESC
    {
    D3D11_FILTER Filter;
    D3D11_TEXTURE_ADDRESS_MODE AddressU;
    D3D11_TEXTURE_ADDRESS_MODE AddressV;
    D3D11_TEXTURE_ADDRESS_MODE AddressW;
    FLOAT MipLODBias;
    UINT MaxAnisotropy;
    D3D11_COMPARISON_FUNC ComparisonFunc;
    FLOAT BorderColor[ 4 ];
    FLOAT MinLOD;
    FLOAT MaxLOD;
    } 	D3D11_SAMPLER_DESC;

이걸 보면서 뭘 하는지 때려맞춰 본다.

AddressU, AddressV는 UV좌표와 관련이 있는데 

1을 초과해서, 2나 3으로 했을 때 빈 부분들을 어떻게 채워줄지, 이런 관련된 부분들을 얘가 정해준다고 보면 된다. 

Sampling 규칙은

float4 PS(VS_OUTPUT input) : SV_Target
{      
    float4 color = texture0.Sample(sampler0, input.uv);
    // texture0에 uv좌표를 이용하여 해당하는 색상을 빼온다.
    
    return color; 
}

어떻게 texture를 이용할지에 관련된 규칙이라고 보면 된다. 

 

void Game::CreateSamplerState()
{
	D3D11_SAMPLER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc));
	// 여기다가 마음에 드는 옵션들을 세팅해 보면된다. 
	desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; 
	desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; 
	desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER; 
	desc.BorderColor[0] = 1; 
	desc.BorderColor[1] = 0; 
	desc.BorderColor[2] = 0; 
	desc.BorderColor[3] = 1; 
	// 여기부턴 별로 안중요해서 그냥 블로그에서 본 아무 값
	desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; 
	desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; 
	desc.MaxAnisotropy = 16; 
	desc.MaxLOD = FLT_MAX; 
	desc.MinLOD = FLT_MIN; 
	desc.MipLODBias = 0.0f; 

	_device->CreateSamplerState(&desc, _samplerState.GetAddressOf()); 
}

문서를 읽어보고 값들을 바꿔 보면 도움이 된다. 

이렇게 desc를 채워주고, 

 

3) Render의 PS에서 RSSetSamplers로 _samplerState 세팅해주기

SamplerState를 적용을 시키려면, 
다시 Render의 PS영역으로 가서 

	_deviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());

이렇게 세팅을 해주면 된다.

 

samplerState가 설정한 것에 따라 적용이 되는 건데 실행했을 때, 아직까지는 별 차이가 없이 이미지가 뜨고 있다. 

 

티가 나게 하기 위 CreateGeometry에서 uv값을 1에서 5로 바꿔 보면 

이미지의 나머지 부분들이 빨강색으로 뜨는 것을 볼 수 있다. 
왜 빨간색으로 뜨는 걸까 ?
CreateSamplerState에서 세팅한 규칙에 의해가지고  D3D11_TEXTURE_ADDRESS_BORDER; 로 해줬기 때문에 이렇게 나온다는 걸 알 수 있다. 

 

4) CreateSamplerState에서 desc 세팅 바꿔주고 실험_MIRROR

void Game::CreateSamplerState()
{
	D3D11_SAMPLER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc));
	// 여기다가 마음에 드는 옵션들을 세팅해 보면된다. 
	desc.AddressU = D3D11_TEXTURE_ADDRESS_MIRROR;
	desc.AddressV = D3D11_TEXTURE_ADDRESS_MIRROR;
	desc.AddressW = D3D11_TEXTURE_ADDRESS_MIRROR;

이렇게 MIRROR로 바꾼다면 

이렇게 나온다는 것을 알 수 있다. 

 

유니티에서 옵션들을 건드려 보면 이런 것들이 있다. 

크게 중요한 건 아니었다. Sample하는 단계에서 어떻게 할 것인가에 관련된 문제였다. 
UI한 개 만들거면 그냥 다 채우는게 낫지 않을까 싶겠지만, 경우에 따라 이미지가 타일처럼 여러개 반복적으로 그리는게 자연스러울 때도 있고, 그 때 그 때 따라가지고 규칙을 정해주면 된다. 

이게 Sampler의 역할이었다. 

 

3. BlendState

마지막으로 BlendState를 알아보자면, 
오늘 배우는 3총사는 앞에서 배운 것들에 비하면 크게 중요한 건 아냐. 
가볍게 알아 보는 걸 목표로 한다. 

1) ComPtr로 BlendState선언

Game.h에  

ComPtr<ID3D11BlendState> _blendState = nullptr;

2) CreateBlendState에서 DESC 세팅 후 _device->CreateBlendState인자로 넣어 _blendState 내용 채우기, Init에서 CreateBlendState

그다음에 CreateBlendState()에서도 마찬가지로 

void Game::CreateBlendState()
{
	D3D11_BLEND_DESC desc; 
	ZeroMemory(&desc, sizeof(D3D11_BLEND_DESC)); 
	desc.AlphaToCoverageEnable = false; 
	desc.IndependentBlendEnable = false; 

	desc.RenderTarget[0].BlendEnable = true; 
	desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; 
	desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; 
	desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; 
	desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; 
	desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; 
	desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; 
	desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; 

	HRESULT hr = _device->CreateBlendState(&desc, _blendState.GetAddressOf()); 
	CHECK(hr); 
}

이렇게 구현한다.

desc에서 채워준 것은 blend에 관한 옵션이다.
BlendEnable을 false로 하면 아무런 효과가 없겠지만, true로 해주면 blending이 되는 거. 
Alpha값에 따라 가지고 어떻게 섞어 줄 지를 규칙을 정해 주는 것이다. 

반투명으로 보여야 한다거나, 벽 앞으로 가면 벽이 투명하게 되어서 캐릭터가 보이게 되어야 한다거나 하는 경우 활용할 수 있다. 

 

3) Render의 OM에서 OMSetBlendState로 _blendState 세팅해주기

만들었으면 Render의 OM 부분에서 적용을 하자.  

// OM
_deviceContext->OMSetBlendState(_blendState.Get(), nullptr, 0xFFFFFFFF);

 

중요하지 않은 부분은 2D 작업을 계속 하면서 알파 블렌딩을 다시 해볼거라서 가볍게 했다. 

 

4. 맺음말

결국 보여주고 싶었던 것은 이런 식의 흐름이 된다는 거다. 렌더링 파이프 라인에 따라 가지고 함수들이 반복해서 등장을 하고, 필요한 거에 따라서 값을  채워 주고 넣어주고 이런 식으로 작업을 하는게 DX11이다라고 일단 보면 된다. 

앞으로 수식을 공부해서 vetexShader, pixelShader에 관련된 기능들에 대해서 더 넣어주긴 하겠지만, 기본적으로는 함수 여러개를 만들어 놓고, 거기다가 인자들을 실시간으로 꽂아 넣어가지고 작동할 수 있게 만들어 주는 것이다 라고 할 수 있다. 

인자들로 중간에 꽂아 넣는 애들은 일종의 함수의 변수처럼 물체마다 고유한 값을 가지고 있어가지고, 그것만 찔러 주게 되면 물체의 변환이나 색상의 변화 등등을 얻어 줄 수 있다. 

몬스터 마다 텍스쳐가 달라진다면 텍스쳐만 교체해주면 나머지 부분들은 똑같이 그려줄 수 있다. 

남은건 반복해서 공부하면서 따라 만들어 보는 거,  특히 초반부 ConstantBuffer나오기 전 까지가 가장 중요하다. 그 부분을 잘 익히고, 익숙해 지면 세부적인 함수 인자를 검색해 보고 하나씩 고쳐 보고 실습하는 것을 권장한다. 

오늘 본 안중요한 거 3총사는 사실 안해도 된다. 나중에 필요하면 공부하면 되고, rasterizer도 초기 값이 잘 되어 있어서 안고쳐도 됐었고, sampelr state도 마찬가지로 만들어서 연동했지만 하기 전에도 잘 동작하고 있었어. 기본상태로 동작하는 것이 다 있기 때문에 필요에 따라 연결을 해서 알아보는 식으로 작업을 해주면 된다. 

BlendState는 2D를 하고 있으니까  한 번 더 나올 예정이다. 

오늘 다뤘던 내용중 가장 중요한 건 렌더링 파이프 라인을 이해를 하고, 어떤 식으로 DIrectX를 이용해서 명령을 내려야 할지를 이해하는 단계가 가장 중요하다고 보면 된다. 

반응형

'DirectX' 카테고리의 다른 글

9. 행렬기초  (0) 2023.12.18
8. 행렬 OT  (0) 2023.12.17
6. ConstantBuffer  (0) 2023.12.13
5. 텍스쳐와 UV  (0) 2023.12.13
4. 삼각형 띄우기  (0) 2023.12.09

댓글