4 분 소요

Texturing

텍스쳐의 개념 자체는 이전에 다루었다

간단한 개념 정리

  • 텍스쳐(Texture)
    GPU 메모리에 있는 리소스 데이터(2D/3D/배열/큐브맵 등)
    컬러뿐 아니라 노멀/높이/그림자/라이팅 LUT 등 임의의 데이터도 저장 가능
    (정확히는 “이미지”라기보다 샘플링 가능한 데이터 버퍼)
    • BindFlag에 따라 해당 사용 목적을 결정
      • D3D11_BIND_RENDER_TARGET : 렌더 타겟으로 출력 가능
      • D3D11_BIND_DEPTH_STENCIL : 깊이/스텐실 버퍼로 이용
      • D3D11_BIND_SHADER_RESOURCE : 쉐이더에서 읽을 수 있도록
  • 텍스쳐링(Texturing)
    셰이더에서 텍스처 데이터를 가져와(샘플링) 표면 셰이딩에 활용하는 전체 과정
    = 샘플링 + 적용(예: 알베도로 곱, 노멀맵으로 TBN 변환, 마스크로 블렌딩 등)
    보통 픽셀 셰이더에서 최종 색 계산에 쓰이지만, 다른 스테이지에서도 SRV로 읽을 수 있음
  • 샘플러(Sampler)
    “어떻게 읽을지”를 정하는 상태 객체
    (필터링(Point/Linear/Aniso), 주소 모드(Wrap/Clamp/Mirror/Border), LOD 범위/바이어스 등을 고려)
    HLSL에선 SamplerState s0; / D3D11에선 ID3D11SamplerState + PSSetSamplers
  • 샘플링(Sampling)
    UV와 샘플러 상태를 바탕으로 LOD 결정 → 주소 보정 → 보간을 수행해 최종 값을 얻는 절차
    (대표 함수: Sample(자동 LOD), SampleLevel(고정 LOD),
    SampleGrad(수동 파생값), SampleCmp(그림자 비교))
    LOD는 보통 화면 파생값(∂u/∂x, …) 로 자동 추정, 축소 시 밉맵 사용
  • SRV(Shader Resource View)
    텍스처(또는 버퍼) 리소스를 셰이더에서 읽게 해주는 “뷰”
    같은 텍스처여도 밉맵 범위, 배열 슬라이스, 포맷 재해석 등을 달리한 여러 SRV를 만들 수 있음
    (파이프라인 바인딩 - PSSetShaderResources(slot, …) → HLSL의 Texture2D : register(t#)와 연결)
    (텍스처 생성 시 BindFlags에 D3D11_BIND_SHADER_RESOURCE 가 포함돼야 SRV 생성 가능)

예시 코드

  • 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 : 모든 상황에서 ‘선형보간’
  • Adress
    주소 모드, ‘UV 좌표’가 0~1 범위를 벗어났을 경우에 대한 대처 방법 지정
    • WRAP : 1을 넘으면 0부터 다시 반복(타일링)
    • CLAMP : 0보다 작으면 0, 1보다 크면 1로 고정 (가장자리 픽셀값 고정)
    • MIRROR : 1을 넘으면 반전해서 반복(반전 타일링)
    • BORDER : 경계색(Border Color)을 사용
  • ComparisonFunc
    비교 샘플러 용 옵션
    (SampleCmp() 같은 함수에서 사용하며, 깊이 텍스쳐 처리시 참/거짓 반환)
    • D3D11_COMPARISON_NEVER : 일반 컬러 샘플러(비교 기능 x)
  • MinLOD / MaxLOD
    LOD(밉맵 레벨) 사용 범위를 제한
    • MinLOD = 0 : 가장 고해상도 밉맵부터 허용
    • MaxLOD = D3D11_FLOAT32_MAX : 제한 없이 모든 밉맵 사용
      (MaxLOD 를 0으로 잡으면 항상 고해상도만 사용한단 뜻이 됨)

그 외 옵션은 나중에 다룰때 설명할 예정


  • 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 타입을 인식가능하다

쉐이더 예제

Image

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); // 샘플러는 같은것을 사용
}

댓글남기기