-
[DirectX11] Sampler의 이해2024년 03월 05일
- 유니얼
-
작성자
-
2024.03.05.:21
728x90DirectX 11로 게임 엔진 아키텍처 만들기
DirectX11을 공부하면서 게임 엔진 아키텍처를 설계하는 것은 매우 흥미롭고 교육적인 경험입니다. 이번 글에서는 DirectX11 내에서 중요한 역할을 하는 Sampler에 대해 알아보겠습니다. Sampler는 텍스처 데이터를 픽셀 셰이더로 가져올 때 사용되는 방식을 정의합니다. 적절한 샘플링은 게임의 비주얼 품질을 대폭 향상시킬 수 있으므로, 이를 잘 이해하고 사용하는 것이 중요합니다.
참고강의 링크:
Sampler란?
Sampler는 텍스처에서 색상 데이터를 어떻게 추출할지 결정하는 객체입니다. 텍스처 좌표가 정확히 텍셀(Texel, 텍스처의 픽셀)에 매핑되지 않을 때, 어떤 텍셀의 색상을 사용할지 결정하는 규칙을 제공합니다. 이는 특히 텍스처가 확대, 축소, 회전되거나, 뷰포트의 픽셀과 정확히 일치하지 않을 때 중요합니다.
기본 구조
먼저, VertexInput 구조체와 VertexOutput 구조체를 정의하여, 각각의 정점 데이터와 픽셀 셰이더로 넘겨줄 데이터를 명시합니다. VS(Vertex Shader)는 입력된 정점 데이터를 화면 상의 위치로 변환하고, PS(Pixel Shader)는 각 픽셀의 최종 색상을 계산합니다.
// 변환 행렬을 정의합니다. 이 행렬들은 3D 오브젝트를 월드 공간, 뷰 공간, 프로젝션 공간으로 변환하는 데 사용됩니다. matrix World; matrix View; matrix Projection; // 텍스처 자원을 정의합니다. Texture2D Texture0; // 텍스처 주소 지정 모드를 결정하기 위한 변수입니다. uint Address; // 버텍스 쉐이더의 입력 구조체입니다. 각 버텍스에 대한 정보를 담고 있습니다. struct VertexInput { float4 position : POSITION; // 버텍스의 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더의 출력 구조체입니다. 처리된 버텍스 정보를 담고 있습니다. struct VertexOutput { float4 position : SV_POSITION; // 스크린 공간에서의 버텍스 위치 float2 uv : TEXCOORD; // 텍스처 좌표 };
SamplerState의 정의와 적용
SamplerState는 텍스처 샘플링 시 텍스처 좌표가 정확한 텍셀에 매칭되지 않을 때, 색상을 어떻게 결정할지 정의합니다. 다음은 다양한 SamplerState의 예시입니다:
- Wrap: 텍스처 좌표가 [0, 1] 범위를 벗어났을 때, 좌표값을 '감싸서' 반복 적용합니다.
- Mirror: 좌표가 범위를 벗어날 때마다 이미지가 거울처럼 반전됩니다.
- Clamp: 좌표가 [0, 1] 범위를 벗어나면, 가장자리의 색상을 계속 사용합니다.
- Border: 지정된 색상으로 경계를 채웁니다.
// 샘플러 상태를 정의합니다. 이 상태는 텍스처 샘플링 시의 주소 지정 모드를 결정합니다. SamplerState Sampler0; // Wrap 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 '반복'합니다. SamplerState SamplerAddressWrap { AddressU = Wrap; AddressV = Wrap; }; // Mirror 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 '반사'하여 반복합니다. SamplerState SamplerAddressMirror { AddressU = Mirror; AddressV = Mirror; }; // Clamp 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 경계값에 '고정'합니다. SamplerState SamplerAddressClamp { AddressU = Clamp; AddressV = Clamp; }; // Border 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 지정된 '경계 색상'을 사용합니다. SamplerState SamplerAddressBorder { AddressU = Border; AddressV = Border; BorderColor = float4(1, 0, 0, 1); // 경계 색상을 빨간색으로 설정 };
픽셀 셰이더에서 SamplerState 사용
PS 함수에서는 Address 변수의 값에 따라 다양한 SamplerState를 선택하여 텍스처를 샘플링합니다. 이를 통해 실행 시간에 텍스처의 샘플링 방식을 변경할 수 있습니다.
// 픽셀 쉐이더 함수입니다. float4 PS(VertexOutput input) : SV_TARGET { // Address 변수의 값에 따라 다른 샘플러를 사용하여 텍스처를 샘플링합니다. if (Address == 0) return Texture0.Sample(SamplerAddressWrap, input.uv); if (Address == 1) return Texture0.Sample(SamplerAddressMirror, input.uv); if (Address == 2) return Texture0.Sample(SamplerAddressClamp, input.uv); if (Address == 3) return Texture0.Sample(SamplerAddressBorder, input.uv); // 기본 샘플러를 사용한 샘플링 return Texture0.Sample(Sampler0, input.uv); }
Shader코드
Sampler.fx
// 변환 행렬을 정의합니다. 이 행렬들은 3D 오브젝트를 월드 공간, 뷰 공간, 프로젝션 공간으로 변환하는 데 사용됩니다. matrix World; matrix View; matrix Projection; // 텍스처 자원을 정의합니다. Texture2D Texture0; // 텍스처 주소 지정 모드를 결정하기 위한 변수입니다. uint Address; // 버텍스 쉐이더의 입력 구조체입니다. 각 버텍스에 대한 정보를 담고 있습니다. struct VertexInput { float4 position : POSITION; // 버텍스의 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더의 출력 구조체입니다. 처리된 버텍스 정보를 담고 있습니다. struct VertexOutput { float4 position : SV_POSITION; // 스크린 공간에서의 버텍스 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더 함수입니다. VertexOutput VS(VertexInput input) { VertexOutput output; // 입력된 버텍스 위치를 월드, 뷰, 프로젝션 행렬을 사용하여 변환합니다. output.position = mul(input.position, World); output.position = mul(output.position, View); output.position = mul(output.position, Projection); // 텍스처 좌표를 출력 구조체로 복사합니다. output.uv = input.uv; return output; } // 샘플러 상태를 정의합니다. 이 상태는 텍스처 샘플링 시의 주소 지정 모드를 결정합니다. SamplerState Sampler0; // Wrap 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 '반복'합니다. SamplerState SamplerAddressWrap { AddressU = Wrap; AddressV = Wrap; }; // Mirror 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 '반사'하여 반복합니다. SamplerState SamplerAddressMirror { AddressU = Mirror; AddressV = Mirror; }; // Clamp 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 좌표를 경계값에 '고정'합니다. SamplerState SamplerAddressClamp { AddressU = Clamp; AddressV = Clamp; }; // Border 모드: 텍스처 좌표가 [0,1] 범위를 벗어났을 때, 지정된 '경계 색상'을 사용합니다. SamplerState SamplerAddressBorder { AddressU = Border; AddressV = Border; BorderColor = float4(1, 0, 0, 1); // 경계 색상을 빨간색으로 설정 }; // 픽셀 쉐이더 함수입니다. float4 PS(VertexOutput input) : SV_TARGET { // Address 변수의 값에 따라 다른 샘플러를 사용하여 텍스처를 샘플링합니다. if (Address == 0) return Texture0.Sample(SamplerAddressWrap, input.uv); if (Address == 1) return Texture0.Sample(SamplerAddressMirror, input.uv); if (Address == 2) return Texture0.Sample(SamplerAddressClamp, input.uv); if (Address == 3) return Texture0.Sample(SamplerAddressBorder, input.uv); // 기본 샘플러를 사용한 샘플링 return Texture0.Sample(Sampler0, input.uv); } // 렌더링 기법을 정의합니다. 여기서는 두 개의 패스를 사용합니다. technique11 T0 { pass P0 { // 첫 번째 패스: 버텍스 쉐이더와 픽셀 쉐이더를 설정합니다. SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } pass P1 { // 두 번째 패스: 동일한 쉐이더를 재사용합니다. SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } };
프로젝트 호출
생성한 Sampler는 픽셀 셰이더에서 사용됩니다. ID3D11DeviceContext::PSSetSamplers() 함수를 호출하여 픽셀 셰이더의 Sampler 슬롯에 바인딩합니다. 이렇게 하면 픽셀 셰이더에서 텍스처를 샘플링할 때 해당 Sampler의 설정을 사용하게 됩니다.
SamplerDemo.h
#pragma once #include "IExecute.h" #include "Geometry.h" #include "GameObject.h" class SamplerDemo : public IExecute { public: void Init() override; void Update() override; void Render() override; shared_ptr<Shader> _shader; //Object shared_ptr<Geometry<VertexTextureData>> _geometry; shared_ptr<VertexBuffer> _vertexBuffer; shared_ptr<IndexBuffer> _indexBuffer; Vec3 _translation = Vec3(0.f, 0.f, 0.f); Matrix _world = Matrix::Identity; //Camera shared_ptr<GameObject> _camera; shared_ptr<Texture> _texture; };
SamplerDemo.cpp
#include "pch.h" #include "06. SamplerDemo.h" #include "GeometryHelper.h" #include "Camera.h" #include "CameraScript.h" #include "Transform.h" #include "Texture.h" void SamplerDemo::Init() { _shader = make_shared<Shader>(L"05. Sampler.fx"); // Object _geometry = make_shared<Geometry<VertexTextureData>>(); //GeometryHelper::CreateQuad(_geometry); //GeometryHelper::CreateCube(_geometry); //GeometryHelper::CreateSphere(_geometry); GeometryHelper::CreateGrid(_geometry, 256, 256); _vertexBuffer = make_shared<VertexBuffer>(); _vertexBuffer->Create(_geometry->GetVertices()); _indexBuffer = make_shared<IndexBuffer>(); _indexBuffer->Create(_geometry->GetIndices()); // Camera _camera = make_shared<GameObject>(); _camera->GetOrAddTransform(); _camera->AddComponent(make_shared<Camera>()); _camera->AddComponent(make_shared<CameraScript>()); _camera->GetTransform()->SetWorldPosition(Vec3(0.f, 10.f, -2.f)); _texture = RESOURCES->Load<Texture>(L"UnityLogo", L"..\\Resources\\Texture\\UnityLogo.png"); } void SamplerDemo::Update() { _camera->Update(); } void SamplerDemo::Render() { _shader->GetMatrix("World")->SetMatrix((float*)&_world); _shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView); _shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection); _shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get()); enum ADDRESS_VALUE { ADDRESS_WRAP = 0, ADDRESS_MIRROR = 1, ADDRESS_CLAMP = 2, ADDRESS_BORDER = 3, }; _shader->GetScalar("Address")->SetInt(ADDRESS_WRAP); uint32 stride = _vertexBuffer->GetStride(); uint32 offset = _vertexBuffer->GetOffset(); DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset); DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0); _shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0); }
결론
Sampler는 DirectX11 게임 엔진에서 텍스처 샘플링 방식을 정의하는 중요한 구성 요소입니다. 적절한 샘플링 방식의 선택은 게임의 비주얼 품질과 성능에 큰 영향을 미칩니다. 다양한 SamplerState를 정의하고 적용함으로써, 게임이나 어플리케이션에서 원하는 비주얼 효과를 효율적으로 구현할 수 있습니다.
반응형다음글이전글이전 글이 없습니다.댓글