Qt6 Round Image com Shader Effect GLSL Vulkan Style

Com o lançamento do Qt 6, tivemos diversas mudanças na parte da API gráfica, sendo introduzido definitivamente o RHI (Rendering Hardware Interface). Além disso, tivemos algumas APIs ou componentes removidos, como é o caso de alguns efeitos do QtGraphicalEffects, por exemplo, que usávamos para fazer o clipping de imagens e deixar com o formato circular.
Como eu precisei fazer isso para um projeto pessoal, resolvi desenvolver utilizando o novo processo de ShaderEffect com Vulkan GLSL, serviu como estudo para mim, e quis compartilhar, já que é um assunto pouco abordado por enquanto.
Vamos ao que interessa!
Primeiro precisamos criar um projeto Qt Quick simples, que pode ser criado com o template do Qt Creator.

Eu escolhi o CMake como build generator, mas isso não é importante no momento, escolha o que for mais confortável para você.
Feita a primeira etapa, iremos criar um novo arquivo qml, para isso clicaremos com o botão direito sobre o arquivo qml.qrc do nosso projeto e escolhemos a opção Add New… e vamos criar um arquivo chamado RoundImage.qml

Aqui, criamos duas propriedades do tipo alias. Na primeira, o caminho da imagem que queremos carregar e na segunda o nível de nitidez da borda.
Esse segundo atributo é opcional e caso não seja passado um valor, ele irá assumir o valor default, deixando a borda bem nítida.
A parte importante desse componente são os atributos vertexShader e fragmentShader respectivamente, que iremos falar deles mais para frente.
Feito isso, podemos incluir nosso novo componente na janela principal, para isso basta montarmos o layout de acordo com o nosso design.
Fiz um simples Column com o RoundImage e um Slider que será usado para controlar a nitidez da borda.

Agora, vamos adicionar os arquivos auxiliares que precisaremos para escrever nossos shaders.
Novamente clique com o botão direito sobre o arquivo qml.qrc que fica na árvore do nosso projeto e adicione um novo arquivo do tipo GLSL > Fragment Shader (Desktop OpenGL) e um GLSL > Vertex Shader (Desktop OpenGL)
Crie um novo diretório na raiz do projeto com o nome shaders, o caminho deve ficar como abaixo.
$ /shaders/roundImage.frag
$ /shaders/roundImage.vert
Adicione também uma imagem ao projeto ou passe uma url qualquer no atributo source do componente RoundImage.

Infelizmente o Qt Creator ainda cria os arquivos com um template base focado em OpenGL. Iremos remover o conteúdo dos arquivos e adicionar o código abaixo.

O que esse shader faz é basicamente passar para o pipeline gráfico a posição dos vértices e as coordenadas de mapeamento da textura, nada muito relevante, sendo que o que importa realmente para nós ficará no fragment shader.

Aqui é onde toda a mágica acontece!
Primeiro calculamos a distância entre a borda e o centro do nosso objeto e armazenamos na variável dist.
Depois iremos criar o delta, que será usado para deixar a borda um um aspecto mais suave, sem aquele serrilhado, porém ao utilizar um valor fixo para esse delta temos um problema que a borda ficaria dependente da resolução que iremos criar nossa imagem.
Poderíamos criar um atributo do tipo uniform e calcular o valor do delta com relação ao tamanho da imagem, porém em GLSL temos uma função que nos ajuda nesse processo, fwidth(x), essa função retorna a soma do valor absoluto das derivadas em x e y, sem entrarmos muito na matemática, basicamente teremos a distância entre dois fragmentos.
Por último, iremos realmente criar a máscara que irá cortar nossa imagem com o formato arredondado. Para isso, criaremos mais uma variável float agora com o nome alpha e utilizaremos a função smoothstep para criar a transição suave da borda. Essa função recebe como parâmetro as duas bordas ou limites e um valor para interpolação, no nosso caso o edge e o delta, após isso apenas precisamos criar nossa textura com a imagem que passamos por parâmetro para o uniform src e misturamos a textura com uma textura vec4 transparente (área recortada), usando nosso alpha calculado anteriormente para gerar o clipping (recorte) final.
Importante: O Qt6 utiliza GLSL Vulkan Style.
Outro ponto importante que gostaria de ressaltar é que agora precisamos pré compilar nosso shader. Isso se faz necessário para que possamos escrever apenas um shader e portar o mesmo para os diversos backend que o Qt trouxe, como OpenGL, Vulkan, DirectX e Metal. Esse processo será feito usando uma ferramenta que está disponível no instalador da Qt, Qt Shader Tools.
É por esse motivo que usamos dentro do componente RoundImage o arquivo com extensão *.qsb e não *.frag como faríamos antigamente.
Utilizaremos o qsb para compilar nosso shader. (Não confundir com Qbs).
O caminho desse “compilador” fica em;
<yourpath>/Qt/6.x.x/gcc_64/bin/qsb
O processo é simples, iremos apontar para os nossos shaders passando alguns parâmetros. Não vou me aprofundar nas flags disponíveis, isso pode ser consultado no manual da ferramenta. Qt Shader Tools
$ qsb -b — glsl 330 -o roundImage.frag.qsb roundImage.frag
$ qsb -b — glsl 330 -o roundImage.vert.qsb roundImage.vert
Caso tudo ocorra corretamente, serão criados dois arquivos com extensão *.qsb, inclua os mesmos no seu qml.qrc.
Pronto, só compilar sua aplicação!

Bom, essa foi a primeira vez que me atrevo a escrever algo publicamente, agradeço se puderem deixar comentários, críticas ou sugestões, para que eu possa melhorar esse e talvez até vir a fazer outros 😎
Link para o projeto
https://github.com/andreagen0r/qmlRoundImage
Originally published at http://github.com.