Direct X 와 텍스쳐링
Texturing
텍스쳐의 개념 자체는 이전에 다루었다
간단한 개념 정리
-
- 텍스쳐(Texture)
- GPU 메모리에 있는 리소스 데이터(2D/3D/배열/큐브맵 등)
컬러뿐 아니라 노멀/높이/그림자/라이팅 LUT 등 임의의 데이터도 저장 가능
(정확히는 “이미지”라기보다 샘플링 가능한 데이터 버퍼)
- BindFlag에 따라 해당 사용 목적을 결정
- D3D11_BIND_RENDER_TARGET : 렌더 타겟으로 출력 가능
- D3D11_BIND_DEPTH_STENCIL : 깊이/스텐실 버퍼로 이용
- D3D11_BIND_SHADER_RESOURCE : 쉐이더에서 읽을 수 있도록
- D3D11_BIND_RENDER_TARGET : 렌더 타겟으로 출력 가능
- BindFlag에 따라 해당 사용 목적을 결정
- 텍스쳐(Texture)
-
- 텍스쳐링(Texturing)
- 셰이더에서 텍스처 데이터를 가져와(샘플링) 표면 셰이딩에 활용하는 전체 과정
= 샘플링 + 적용(예: 알베도로 곱, 노멀맵으로 TBN 변환, 마스크로 블렌딩 등)
보통 픽셀 셰이더에서 최종 색 계산에 쓰이지만, 다른 스테이지에서도 SRV로 읽을 수 있음
- 텍스쳐링(Texturing)
-
- 샘플러(Sampler)
- “어떻게 읽을지”를 정하는 상태 객체
(필터링(Point/Linear/Aniso), 주소 모드(Wrap/Clamp/Mirror/Border), LOD 범위/바이어스 등을 고려)
HLSL에선 SamplerState s0; / D3D11에선 ID3D11SamplerState + PSSetSamplers
- 샘플러(Sampler)
-
- 샘플링(Sampling)
- UV와 샘플러 상태를 바탕으로 LOD 결정 → 주소 보정 → 보간을 수행해 최종 값을 얻는 절차
(대표 함수: Sample(자동 LOD), SampleLevel(고정 LOD),
SampleGrad(수동 파생값), SampleCmp(그림자 비교))
LOD는 보통 화면 파생값(∂u/∂x, …) 로 자동 추정, 축소 시 밉맵 사용
- 샘플링(Sampling)
-
- SRV(Shader Resource View)
- 텍스처(또는 버퍼) 리소스를 셰이더에서 읽게 해주는 “뷰”
같은 텍스처여도 밉맵 범위, 배열 슬라이스, 포맷 재해석 등을 달리한 여러 SRV를 만들 수 있음
(파이프라인 바인딩 - PSSetShaderResources(slot, …) → HLSL의 Texture2D : register(t#)와 연결)
(텍스처 생성 시 BindFlags에 D3D11_BIND_SHADER_RESOURCE 가 포함돼야 SRV 생성 가능)
- SRV(Shader Resource View)
예시 코드
- Texture2D와 ShaderResource View 생성 부분
void AppBase::CreateTexture(
const std::string filename, ComPtr<ID3D11Texture2D> &texture,
ComPtr<ID3D11ShaderResourceView> &textureResourceView) {
int width, height, channels;
// stbi_load 를 통해, 이미지를 가져온다
unsigned char *img =
stbi_load(filename.c_str(), &width, &height, &channels, 0);
//assert(channels == 4);
std::vector<uint8_t> image;
image.resize(width * height * channels);
memcpy(image.data(), img, image.size() * sizeof(uint8_t));
// Create texture.
D3D11_TEXTURE2D_DESC txtDesc = {};
txtDesc.Width = width;
txtDesc.Height = height;
txtDesc.MipLevels = txtDesc.ArraySize = 1;
txtDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // vector<uint8_t> 를 이용하기에 UNORM 사용
txtDesc.SampleDesc.Count = 1;
txtDesc.Usage = D3D11_USAGE_IMMUTABLE; // 딱히 이 리소스 내부를 변형할 생각이 없음
txtDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE; // 쉐이더에서 쓸 것임!
// Fill in the subresource data.
D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = image.data();
InitData.SysMemPitch = txtDesc.Width * sizeof(uint8_t) * channels;
// InitData.SysMemSlicePitch = 0;
// ID3D11Device* pd3dDevice; // Don't forget to initialize this
// TODO: You should really consider using a COM smart-pointer like
// Microsoft::WRL::ComPtr instead
m_device->CreateTexture2D(&txtDesc, &InitData, texture.GetAddressOf());
m_device->CreateShaderResourceView(texture.Get(), nullptr,
textureResourceView.GetAddressOf());
}
- 그렇게 만든 텍스쳐와 SRV, 그리고
Sampler state 만들기
AppBase::CreateTexture("crate2_diffuse.png", m_texture,
m_textureResourceView);
// Texture sampler 만들기
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory(&sampDesc, sizeof(sampDesc));
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
// Create the Sample State
m_device->CreateSamplerState(&sampDesc, m_samplerState.GetAddressOf());
Sampler State의 세부 설정에 대하여
-
- Filter
- 텍스쳐 축소/확대/밉맵 선택에 따른 ‘보간’ 방법 지정
- MIN : 텍스쳐 축소시
- MAG : 텍스쳐 확대시
- MIP : 밉맵 선택시
이걸 기반으로 보는 예시들
- D3D11_FILTER_MIN_MAG_MIP_LINEAR : 모든 상황에서 ‘선형보간’
- Filter
-
- Adress
- 주소 모드, ‘UV 좌표’가 0~1 범위를 벗어났을 경우에 대한 대처 방법 지정
- WRAP : 1을 넘으면 0부터 다시 반복(타일링)
- CLAMP : 0보다 작으면 0, 1보다 크면 1로 고정 (가장자리 픽셀값 고정)
- MIRROR : 1을 넘으면 반전해서 반복(반전 타일링)
- BORDER : 경계색(Border Color)을 사용
- Adress
-
- ComparisonFunc
- 비교 샘플러 용 옵션
(SampleCmp() 같은 함수에서 사용하며, 깊이 텍스쳐 처리시 참/거짓 반환)
- D3D11_COMPARISON_NEVER : 일반 컬러 샘플러(비교 기능 x)
- ComparisonFunc
-
- MinLOD / MaxLOD
- LOD(밉맵 레벨) 사용 범위를 제한
- MinLOD = 0 : 가장 고해상도 밉맵부터 허용
- MaxLOD = D3D11_FLOAT32_MAX : 제한 없이 모든 밉맵 사용
(MaxLOD 를 0으로 잡으면 항상 고해상도만 사용한단 뜻이 됨)
- MinLOD / MaxLOD
그 외 옵션은 나중에 다룰때 설명할 예정
- Pixel Shader로 보내주기
ID3D11ShaderResourceView *pixelResources[1] = {m_textureResourceView.Get()};
m_context->PSSetShaderResources(0, 1, pixelResources);
m_context->PSSetSamplers(0, 1, m_samplerState.GetAddressOf());
SRV를 만들 때, 텍스쳐 리소스를 가리키게 만듦
그렇게 보내줄때는 SRV만 보내준다
(어떤 포맷으로 읽을지도 포함)
(t0 슬롯에 바인딩)
(t : 텍스쳐)
만약 여러개 보낸다면
HLSL
Texture2D g_texture0 : register(t0);
Texture2D g_texture1 : register(t1);
Texture2D g_texture2 : register(t2);
// 아니면 배열로 받을 수 있음
// Texture2D g_textures[4] : register(t0); // t0~t3 차지
SamplerState g_samp0 : register(s0);
SamplerState g_samp1 : register(s1);
SamplerState g_samp2 : register(s2);
---
DX C++
ID3D11ShaderResourceView* srvs[] = {
srv0.Get(), srv1.Get(), srv2.Get()
};
context->PSSetShaderResources(/*StartSlot=*/0, /*NumViews=*/_countof(srvs), srvs);
ID3D11SamplerState* samplers[] = {
samp0.Get(), samp1.Get(), samp2.Get()
};
context->PSSetSamplers(/*StartSlot=*/0, /*NumSamplers=*/_countof(samplers), samplers);
이런식으로 NumViews 쪽의 변수를
개수에 맞게 바꿔주면 된다
- Pixel Shader에서 직접 texture를 받음
ColorPixelShader.hlsl
---
Texture2D g_texture0 : register(t0);
SamplerState g_sampler : register(s0);
cbuffer PixelShaderConstantBuffer : register(b0) { float xSplit; };
struct PixelShaderInput {
float4 pos : SV_POSITION;
float3 color : COLOR;
float2 texcoord : TEXCOORD;
};
float4 main(PixelShaderInput input) : SV_TARGET {
return input.texcoord.x > xSplit
? g_texture0.Sample(g_sampler, input.texcoord)
: float4(1.0, 0.0, 0.0, 1.0);
}
srv로 보낸 Texture 타입을 인식가능하다
쉐이더 예제
xsplit에 맞게 두 텍스쳐를 나누어 텍스쳐링하는 예제
- 텍스쳐와 SRV 를 헤더에 선언
Example.h
ComPtr<ID3D11Texture2D> m_wallTexture;
ComPtr<ID3D11ShaderResourceView> m_wallTextureResourceView;
추가
(Sampler State는 동일하게 사용)
- 텍스쳐와 SRV 생성
Example.app - Initialize()
AppBase::CreateTexture("wall.jpg", m_wallTexture,
m_wallTextureResourceView);
- 이후 SRV를 이전 텍스쳐와 같이 보내준다
Example.app - Render()
ID3D11ShaderResourceView *pixelResources[] = {
m_textureResourceView.Get(), m_wallTextureResourceView.Get()};
m_context->PSSetShaderResources(0, _countof(pixelResources), pixelResources);
- Pixel Shader에서 텍스쳐를 추가로 받고
이후 xSplit에 따라 출력할 텍스쳐 결정
Texture2D g_texture0 : register(t0);
Texture2D g_texture1 : register(t1); // 신규 텍스쳐
SamplerState g_sampler : register(s0);
cbuffer PixelShaderConstantBuffer : register(b0) { float xSplit; };
struct PixelShaderInput {
float4 pos : SV_POSITION;
float3 color : COLOR;
float2 texcoord : TEXCOORD;
};
float4 main(PixelShaderInput input) : SV_TARGET {
return input.texcoord.x > xSplit
? g_texture0.Sample(g_sampler, input.texcoord)
: g_texture1.Sample(g_sampler, input.texcoord); // 샘플러는 같은것을 사용
}
댓글남기기