[목차]
- Rasterizer
- RasterizerState
- 결과물 확인
[Rasterizer]
Rasterizer는 3D 정점 데이터를 2D 픽셀로 변환하는 렌더링 파이프라인 단계입니다. 픽셀화 단계라고도 할 수 있습니다. 해당 단계에서는 정점을 픽셀로 변환하거나, 그려질 필요가 없는 픽셀들을 Culling하는 과정을 진행합니다.
1. 정점을 픽셀로 변환(Resterization)
: Vertex Shader를 통해 Clip Space 좌표계로 변환된 정점들은 Rasterizer 단계에서 W Divide를 통해 NDC 좌표값으로 변경되고, 뷰포트 행렬 적용을 통해 Screen Space 좌표계로 변환된 후 Pixel Shader로 전달됩니다. 원래는 정점마다 데이터(시멘틱)들을 담고 있었지만, 해당 단계를 통해 픽셀마다 데이터를 담는 형식으로 변경됩니다. 삼각형이 있다면, GPU는 세모 내부의 모든 픽셀을 찾아 Pixel Shader에 전달하는 것입니다.
2. 백페이스 컬링(Back-face Culling)
: 삼각형이 카메라의 반대 방향을 향하고 있다면 렌더링하지 않도록 합니다. 여기서 D3D11_RASTERIZER_DESC의 CullMode, FrontCounterClockwise의 값을 통해 어떻게 진행할지 확인합니다.
3. 스크린 클리핑(절두체 컬링)
: 삼각형의 일부가 화면 바깥에 위치할 경우(NCD x, y, z 범위가 -1.0 ~ 1.0을 벗어난 삼각형), GPU는 삼각형을 자르거나 무시하게 됩니다.
4. 뷰포트 변환(1번과정+)
: 당연히 화면에 띄워야하기 떄문에 뷰포트 변환을 수행합니다. NDC 좌표계에 있는 x, y, z값을 스크린 공간의 x, y, z로 옮겨줍니다.
x_screen = (ndc.x + 1) * 0.5 * ViewportWidth + ViewportTopLeftX;
y_screen = (1 - ndc.y) * 0.5 * ViewportHeight + ViewportTopLeftY; // y는 상하 반전
z_depth = (ndc.z + 1) * 0.5;
5. 스캔 변환
: 삼각형 내부 픽셀을 결정하기 위해 수학적 계산(Barycentric 좌표계)을 사용합니다. 픽셀이 삼각형 내부인지 여부를 판단하고 Pixel Shader로 보냅니다.
6. 속성(attribute) 보간
: 정점마다 정의된 UV, Normal, Color등의 값을 픽셀 단위로 보간합니다. 따라서 우리의 프로젝트에서 Color를 각 정점마다 빨, 파, 초로 설정했어도 이를 통해 3가지 색상이 보간되어 나타난 것입니다.
[RasterizerState]
이 단계도 DepthStencilState와 마찬가지로 RasterizerState를 통해 사용자 임의로 정의할 수 있습니다. RasterizerState를 따로 지정하지 않으면 디폴트 RasterizerState가 사용되는데, 기본값은 다음과 같습니다.
| FillMode | D3D11_FILL_SOLID | - 폴리곤 면을 채워서 렌더링 |
| CullMode | D3D11_CULL_BACK | - Back-Face Culling 활성화 - 뒷면은 안그린다는 뜻 |
| FrontCounterClockwise | FALSE | - 뒷면 기준은 시계방향 - Index Buffer로 전달한 정점 그리는 순서에서 영향 받음 - 법선이 시계 방향 외적을 통해 도출되는지, 반시계 방향 외적을 통해 도출되는지에 따라 앞면 뒷면을 결정할 수 있음 |
| DepthBias | 0 | - 깊이 Bias 없음 |
| DepthBiasClamp | 0.0f | - 깊이 Bias 제한 없음 |
| SlopeScaledDepthBias | 0.0f | - 깊이 기울기 보정 없음 |
| DepthClipEnable | TRUE | - 절두체 컬링 활성화 - 뷰 밖은 컬링 실시 |
| ScissorEnable | FALSE | - 시저 테스트 비활성화 |
| MultisampleEnable | FALSE | - 멀티샘플링 비활성화 |
| AntialiasedLineEnable | FALSE | - 안티앨리어싱 비활성화 |
우리가 사용할 방식은 여기서 FrontCounterClockwise만 true로 바꾸는 것인데, 이렇게 하는 이유가 있습니다. Rect를 예시로 보겠습니다. 현재 프레임워크에서 Rect는 다음과 같이 생성됩니다.
// 사각형
std::vector<Ext_DirectXVertexData> Vertices;
Vertices.resize(4);
ArrVertex[0] = { { 0.5f, 0.5f, -0.5f, 1.0f}, {1, 0, 0, 1}, {1, 0}, {0, 0, -1} };
ArrVertex[1] = { {-0.5f, 0.5f, -0.5f, 1.0f}, {0, 1, 0, 1}, {0, 0}, {0, 0, -1} };
ArrVertex[2] = { {-0.5f, -0.5f, -0.5f, 1.0f}, {0, 0, 1, 1}, {0, 1}, {0, 0, -1} };
ArrVertex[3] = { { 0.5f, -0.5f, -0.5f, 1.0f}, {1, 1, 0, 1}, {1, 1}, {0, 0, -1} };
std::vector<UINT> ArrIndex = { 0, 1, 2, 0, 2, 3, };
Ext_DirectXVertexBuffer::CreateVertexBuffer("Rect", Vertices);
Ext_DirectXIndexBuffer::CreateIndexBuffer("Rect", ArrIndex);
정점을 그리는 순서가 [0 -> 1 -> 2, 0 -> 2 -> 3]인데, 이건 아래 그림과 같습니다.

여기서 삼각형 1번을 통해 외적 결과가 어떻게 되는지 확인해보겠습니다. [0 -> 1]벡터와 [1 -> 2]벡터는 지금 우리가 바라보는 기준으로 각각 왼쪽(←), 아래(↓)를 향하고 있습니다. 이제 이 둘을 외적하면 법선 벡터가 하나 생성되는데, 그 벡터는 우리를 향하는 방향을 가집니다.
이렇게 생성되는 법선 벡터를 통해 물체의 앞, 뒷면을 정의하고 렌더링하는 것이 3D 렌더링의 핵심 기술입니다. 그러면 이제 이 결과는 현재의 프레임워크에서 어떻게 그려질까요?
모든 Object(카메라 포함)들은 최초에 생성될 때 위치, 회전값이 모두 0인 상태로 생성됩니다. 우리는 최초에 카메라를 생성할 때, 뷰, 프로젝션 행렬을 만들어내면서 아래와 같이 생성했습니다.

| +X | 오른쪽 |
| -X | 왼쪽 |
| +Y | 위 |
| -Y | 아래 |
| +Z | 앞 |
| -Z | 뒤 |
이 좌표는 다른 Object들에게도 똑같이 적용됩니다. CreateActor나 CreateComponent를 실시하면 Object는 위치, 회전이 0인 상태에서 +Z가 앞인 상태가 됩니다. 이때 [0 -> 1 -> 2, 0 -> 2 -> 3]의 순서를 갖는 Rect를 그대로 그려버리면 어떻게 될까요? 디폴트 Rasterizer는 CW가 앞면이면서 Back-Face Culling을 실시하는 상태입니다.

Rect가 컬링되어 버립니다. 아래와 같은 상황입니다.

가끔 필요에 의해 NONE을 실시하여 Culling을 하지 않을 때도 있지만, Back-Face Culling은 그릴 필요가 없는 부분을 그리지 않기 때문에 렌더링 연산량을 획기적으로 줄일 수 있습니다. 이 현상은 보통 우리가 게임을 하다 보면 가끔 접할 수 있습니다. 스피드런 하시는 게이머분들을 보면 미니맵을 뚫고 이동할때가 많을겁니다.


이걸 위해서 ReasterizerState 값을 변경해줘야합니다. 다음과 같이 RasterizerState 값을 지정해줬습니다.
// DirectX11 Rasterizer 생성
void Ext_DirectXResourceLoader::MakeRasterizer()
{
D3D11_RASTERIZER_DESC Desc = {};
Desc.CullMode = D3D11_CULL_BACK; // 뒷면 제거(백페이스 컬링 활성화)
Desc.FrontCounterClockwise = TRUE; // 반시계방향이 앞면(앞면 기준을 CCW로 지정)
Desc.FillMode = D3D11_FILL_SOLID; // 어떻게 그릴것인지, solid는 채우는 형태로 그려줌
Desc.DepthClipEnable = TRUE;
Ext_DirectXRasterizer::CreateRasterizer("EngineRasterizer", Desc);
}
이렇게한 뒤 CreateRasterizer를 실시하면 아래의 함수들이 호출됩니다.
// ID3D11RasterizerState 생성, 와이어프레임과 솔리드 상태 모두 미리 생성함, 설정 상태는 D3D11_FILL_SOLID임
void Ext_DirectXRasterizer::CreateRasterizer(const D3D11_RASTERIZER_DESC& _Value)
{
Release();
RaterizerInfo = _Value;
RaterizerInfo.FillMode = D3D11_FILL_WIREFRAME;
if (S_OK != Ext_DirectXDevice::GetDevice()->CreateRasterizerState(&RaterizerInfo, &WireframeState))
{
MsgAssert("와이어 프레임 레스터라이저 스테이트 생성에 실패했습니다.");
}
RaterizerInfo.FillMode = D3D11_FILL_SOLID;
if (S_OK != Ext_DirectXDevice::GetDevice()->CreateRasterizerState(&RaterizerInfo, &SolidState))
{
MsgAssert("솔리드 레스터라이저 스테이트 생성에 실패했습니다.");
}
// 생성된 이후에는 언제든지 이 함수를 호출해서 세팅 가능(솔리드와 와이어)
SetFILL_MODE(D3D11_FILL_SOLID);
}
// 레스터라이저 세팅, CurState는 솔리드라서 솔리드 레스터라이저로 세팅됨
void Ext_DirectXRasterizer::Setting()
{
if (nullptr == CurState)
{
MsgAssert("생성되지 않은 레스터라이저 스테이트 입니다.");
}
Ext_DirectXDevice::GetContext()->RSSetState(CurState);
}
DepthStencilState와 마찬가지로 생성된 RasterizerState를 Material에 바인딩해줍니다.
// 일반(단일 메시)
{
std::shared_ptr<Ext_DirectXMaterial> NewRenderingPipeline = Ext_DirectXMaterial::CreateMaterial("Basic");
NewRenderingPipeline->SetVertexShader("Basic_VS");
NewRenderingPipeline->SetPixelShader("Basic_PS");
NewRenderingPipeline->SetBlendState("BaseBlend"); // 아직 없음
NewRenderingPipeline->SetDepthState("EngineDepth");
NewRenderingPipeline->SetRasterizer("EngineRasterizer");
}
[결과물 확인]
만약 아예 Rasterizer 세팅을 따로 진행하지 않았다면, 기본적으로 백페이스 Culing이 CW를 앞면으로 하여 설정됩니다. 그래서 Rect의 인덱스 버퍼 설정을 { 0, 1, 2, 0, 2, 3 }으로 했다면 아무것도 보이지 않을 것입니다.

하지만 래스터라이저를 세팅하면서 CCW(반시계 방향)를 앞면으로, 백페이스 컬링을 명시해주면 잘 출력되는 것을 확인할 수 있습니다.

제대로 적용됐는지 확인하려면 회전시켜보면 됩니다.
#include "PrecompileHeader.h"
#include "RectActor.h"
#include <DirectX11_Extension/Ext_MeshComponent.h>
#include <DirectX11_Extension/Ext_Transform.h>
void RectActor::Start()
{
GetTransform()->SetLocalPosition({ 0, 0, 2 });
MeshComp = CreateComponent<Ext_MeshComponent>("BasicMesh");
MeshComp->CreateMeshComponentUnit("Rect", "Basic");
// MeshComp->SetTexture("Cuphead.png");
// MeshComp->SetTexture("Cuphead.png");
MeshComp->GetTransform()->SetLocalPosition({ 0, 0, 0 });
}
void RectActor::Update(float _DeltaTime)
{
__super::Update(_DeltaTime);
float MoveSpeed = 100.0f; // 초당 100 단위 회전
MeshComp->GetTransform()->AddLocalRotation({ 0.f, MoveSpeed * _DeltaTime, 0.f });
}
'DirectX11 > 프레임워크 제작' 카테고리의 다른 글
| [DirectX11] Texcoord(UV)와 Sampler (0) | 2025.05.28 |
|---|---|
| [DirectX11] DirectXTex를 통한 Texture 활용 (0) | 2025.05.28 |
| [DirectX11] DepthStencilState 생성 (0) | 2025.05.28 |
| [DirectX11] 렌더링 파이프라인 설정 결과물 출력 (0) | 2025.05.26 |
| [DirectX11] Object마다의 렌더링 파이프라인 생성을 위한 구조 설정 (0) | 2025.05.26 |