Link

Criando um sampler

Samplers (amostradores) definem um conjunto de parâmetros que controlam como os dados da imagem são carregados (amostrados) dentro dos shaders. Esses parâmetros incluem cálculos de endereço (ou seja, quebra ou repetição), filtragem (linear ou mais próxima) ou uso de mapeamentos MIP. Para usar samplers de dentro dos shaders, precisamos primeiro criá-los.

Criaremos uma função createTextureSampler para configurar esse objeto sampler. Usaremos esse sampler para ler as cores da textura no fragment shader mais tarde.

void Renderer::createTextureSampler() {

}

O sampler não faz referência a um objeto VkImage em qualquer lugar. O sampler é um objeto distinto que fornece uma interface para extrair cores de uma textura. Pode ser aplicado a qualquer imagem desejada, seja 1D, 2D ou 3D. No OpenGL, quando criamos uma textura, a imagem e seus parâmetros de amostragem (sampling) precisavam ser especificados em um único estado. Em versões mais recentes do OpenGL, também poderíamos criar objetos sampler separados. Dentro de um shader, geralmente criamos variáveis do tipo sampler2D, que também combinam as imagens e seus parâmetros de amostragem (samplers). No Vulkan, precisamos criar imagens e samplers separadamente.

Como o objeto de sampler é independente do objeto de imagem, isso significa que não precisamos recriá-lo sempre que houver alguma mudança na imagem de textura, portanto chamaremos a função createTextureSampler em initResources antes de initObject:

void Renderer::initResources() {
	...
	createTextureSampler();
	initObject();
}

Os parâmetros de amostragem são especificados com uma variável do tipo VkSamplerCreateInfo.

VkSamplerCreateInfo samplerInfo = {};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.pNext = nullptr;
samplerInfo.flags = 0;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;

Os campos magFilter e minFilter especificam o modo de filtragem a ser usado quando a imagem é, respectivamente, ampliada ou reduzida. Se uma imagem é ampliada ou reduzida é determinada pela comparação de coordenadas de amostragem entre pixels adjacentes. Se o gradiente das coordenadas de amostragem for maior que um, a imagem será reduzida; caso contrário, é ampliada. magFilter e minFilter são ambos membros da enumeração VkFilter. Os membros de VkFilter são:

  • VK_FILTER_NEAREST: Ao amostrar, o texel mais próximo da imagem é escolhido e retornado diretamente ao shader.
  • VK_FILTER_LINEAR: Uma amostra 2×2 contendo as coordenadas do texel é usada para produzir uma média ponderada de quatro texels, e essa média é retornada ao shader.

O modo VK_FILTER_NEAREST faz com que o Vulkan simplesmente selecione o texel mais próximo das coordenadas solicitadas ao fazer amostragem de uma imagem. Em muitos casos, isso pode levar a uma imagem serrilhada (aliased), causando artefatos cintilantes na imagem renderizada. O modo VK_FILTER_LINEAR diz ao Vulkan para aplicar a filtragem linear à imagem quando ela é amostrada. Quando estamos filtrando uma imagem com filtragem linear, a amostra solicitada pode estar em algum lugar entre dois centros texel em 1D, quatro centros em 2D e assim por diante. O Vulkan lerá os texels ao redor e combinará os resultados usando uma soma ponderada dos valores com base na distância de cada centro.

samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;

Todos esses campos se aplicam ao mapeamento MIP. Não utilizaremos esse recurso aqui.

samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;

Os próximos três campos – addressModeU, addressModeV e addressModeW – são usados para selecionar a transformação aplicada às coordenadas de textura que, de outra forma, seriam amostradas fora da imagem. Os seguintes modos estão disponíveis:

  • VK_SAMPLER_ADDRESS_MODE_REPEAT: Isso repete a textura ao ir além das dimensões da imagem. Isso produz padrões repetidos.
  • VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: Isso também produz um padrão repetitivo, mas com texels adjacentes espelhados.
  • VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: Isso repete os texels da borda mais próxima para as coordenadas que estão além das dimensões da imagem.
  • VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE: Isso é semelhante ao anterior, mas em vez de usar a borda mais próxima da coordenada, usa a borda oposta à borda mais próxima.
  • VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: Isso retorna uma cor sólida ao amostrar além das dimensões da imagem.

Não importa qual modo de endereçamento usamos aqui, porque não vamos amostrar fora da imagem neste projeto.

samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0;

Se quisermos usar a filtragem anisotrópica, definimos anisotropyEnable como VK_TRUE. Os detalhes exatos da filtragem anisotrópica são dependentes da implementação. A filtragem anisotrópica geralmente funciona considerando uma área projetada da área a ser amostrada, em vez de usar uma cobertura fixa de 2×2. Uma aproximação para uma amostra de área é formada tomando muitas amostras dentro da cobertura da área projetada.

Como o número de amostras tiradas pode ser bastante grande, a filtragem anisotrópica pode ter um efeito negativo no desempenho. Além disso, em casos extremos, a área de cobertura projetada pode ser bastante grande, e isso pode resultar em uma área grande e em um resultado de filtro embaçado. Para limitar esses efeitos, podemos fixar a quantidade máxima de anisotropia configurando maxAnisotropy para um valor entre 1.0 e o valor máximo suportado pelo dispositivo. Podemos determinar isso chamando vkGetPhysicalDeviceProperties e inspecionando o membro maxSamplerAnisotropy da estrutura VkPhysicalDeviceLimits. Não usaremos esse recurso aqui, portando definimos anisotropyEnable como VK_FALSE e maxAnisotropy como 1.0.

samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;

O campo borderColor especifica qual cor é retornada ao amostrar além da imagem com modo VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER. Essa não é uma especificação de cor completa, mas um membro da enumeração VkBorderColor, que permite que um conjunto pequeno e predefinido de cores seja selecionado. Esses são:

  • VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK: Retorna zeros de ponto flutuante ao shader em todos os canais
  • VK_BORDER_COLOR_INT_TRANSPARENT_BLACK: Retorna zeros inteiros ao shader em todos os canais
  • VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK: Retorna zeros de ponto flutuante nos canais R, G e B e um em ponto flutuante em A
  • VK_BORDER_COLOR_INT_OPAQUE_BLACK: Retorna zeros inteiros em R, G e B e um inteiro em A
  • VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE: Retorna uns em ponto flutuantes ao shader em todos os canais
  • VK_BORDER_COLOR_INT_OPAQUE_WHITE: Retorna uns inteiros ao shader em todos os canais

Novamente, não importa qual cor usamos aqui, porque não vamos amostrar fora da imagem.

samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;

Se uma função de comparação estiver ativada, os texels serão comparados primeiro a um valor e o resultado dessa comparação será usado nas operações de filtragem. Isso pode ser usado para implementar uma técnica conhecida como filtragem por porcentagem mais próxima (PCF, do inglês percentage closer filtering). Não utilizaremos esse recurso aqui, portanto definimos compareEnable como VK_FALSE.

samplerInfo.unnormalizedCoordinates = VK_FALSE;

Por fim, unnormalizedCoordinates é um sinalizador que, quando definido como VK_TRUE, indica que as coordenadas usadas para amostrar a imagem estão em unidades de texels brutas, ou seja, entre (0, 0) e (largura da textura, altura da textura), em vez de um valor normalizado entre (0,0) e (1,1). Isso permite que os texels sejam explicitamente buscados na imagem. No entanto, existem várias restrições nesse modo. Quando unnormalizedCoordinates for VK_TRUE, minFilter e magFilter devem ser iguais, mipmapMode deve ser VK_SAMPLER_MIPMAP_MODE_NEAREST e anisotropyEnable e compareEnable devem ser VK_FALSE. Aplicativos do mundo real quase sempre usam coordenadas normalizadas, pois é possível usar texturas de resoluções variadas com exatamente as mesmas coordenadas.

Com isso, o funcionamento do sampler está totalmente definido. Agora, adicionamos um novo membro de classe em Renderer para armazenar o manipulador do objeto VkSampler:

VkSampler m_textureSampler = nullptr;
...

O objeto VkSampler é criado chamando a função vkCreateSampler, para a qual fornecemos um ponteiro para a estrutura descrita acima:

VkDevice device = m_window->device();
VkResult result = m_deviceFunctions->vkCreateSampler(
	device,
	&samplerInfo,
	nullptr,
	&m_textureSampler
);
if (result != VK_SUCCESS) {
	qFatal("Failed to create texture sampler: %d", result);
}

Destruímos o sampler no final do programa quando não estiver mais acessando a imagem:

void Renderer::releaseResources() {
	...

	m_deviceFunctions->vkDestroySampler(
		device,
		m_textureSampler,
		nullptr
	);
}

Na próxima seção, vamos expor os objetos de imagem e sampler aos shaders para desenhar a textura no quadrado.


Anterior Próximo