Desfoque da imagem de fundo do Unity



Uma das tarefas que um desenvolvedor de aplicativos Unity pode encontrar é colocar a imagem atual em segundo plano para chamar a atenção do usuário para algo novo, como um menu ou mensagem que aparece. O artigo fala sobre a experiência de resolver esse problema por um desenvolvedor que possui conhecimentos básicos no Unity, sem usar recursos externos ou uma licença adicional do Unity. Espero que este material seja útil para aqueles que enfrentaram um problema semelhante e não encontraram soluções eficazes em diferentes estágios do mesmo.

Destacar elementos importantes da interface (e remover elementos desnecessários) é uma das principais ferramentas para melhorar a experiência do usuário. Isso é especialmente pronunciado ao trabalhar com aplicativos móveis. Essa troca de atenção pode ser feita de diferentes maneiras - afastando suavemente os botões além das bordas da tela, escurecendo o fundo, o comportamento dinâmico, etc. Isso inclui desfocar a imagem. Esse efeito, por um lado, reduz o contraste dos elementos do plano de fundo e fica mais difícil para os olhos perceberem. Por outro lado, o desfoque tem um efeito subconsciente quando a imagem fica desfocada, e a percebemos de maneira diferente. Essa abordagem é freqüentemente usada em jogos e, a partir de exemplos conhecidos de jogos no Unity, podemos nomear os momentos em que escolhemos o menu de missões de Hearthstone ou emtela de conclusão do duelo .

Agora imagine que queremos fazer o mesmo ou semelhante em nosso aplicativo favorito. Também não temos orçamento e pouca experiência com o Unity.

Parte Um - Shader


O primeiro pensamento que surgiu foi que tudo já deveria ser realizado. Certamente usando um shader. E certamente deve estar na caixa Unity. Uma tentativa rápida de pesquisa e instalação mostrou que existe um sombreador no Unity, mas, como se vê, ele faz parte do Standard Assets e não está disponível para uma licença gratuita.

Mas isso é borrão! Um efeito básico que é matematicamente simples e também deve ser facilmente implementado e integrado ao projeto. Mesmo se não soubermos nada sobre shaders. Em seguida, acesse a Internet e a pesquisa mostrou rapidamente que existem códigos-fonte de shaders prontos para o efeito e até diferentes.

Para a primeira implementação, esse shader foi utilizado . Para testá-lo, basta adicionar o sombreador como um arquivo de origem ao projeto, associá-lo ao material, vincular o material à imagem.

Parte Dois - Interface do Usuário


Se nos preocupamos com o usuário do aplicativo, é necessário prestar a devida atenção à interface do usuário. Se você apenas adicionar um efeito de desfoque ao fundo e esquecê-lo, isso nem tudo será consistente com os princípios internos da criação de um produto.

Como resultado, para ações posteriores, você precisa tocar no que aconteceu e, em seguida, pensar e ajustar de forma a causar a percepção necessária do usuário. Este ponto é altamente sensível ao contexto. Dependendo do contraste da imagem, a variedade de cores e outros fatores, vários valores e ferramentas adicionais podem ser selecionados. No nosso caso, nesse estágio, foi obtido um bom resultado com um raio de desfoque de 25 (parâmetro shader) e um Transparente adicional de 70% (fora do shader através de uma imagem separada). No entanto, esta é apenas a imagem de fundo final. A transição em si, de acordo com os sentimentos no telefone, foi muito acentuada.

O sombreador instalado na imagem é calculado em cada quadro e, portanto, para fazer uma alternância suave, basta alterar dinamicamente o parâmetro do raio de desfoque e a transparência. Você pode organizar isso de maneiras diferentes, mas, em essência, o processamento é uma atualização nos manipuladores de atualizações, dependendo do horário. A transição em si na versão final do aplicativo é de 0,2 segundos, mas, como se vê, é importante para a percepção do usuário.

Parte Três - Produtividade


Nos últimos meses, ouvi reclamações de vários usuários diferentes (nem mesmo de programadores) de que os programas de jogos costumam usar todos os recursos de computador disponíveis ociosos e sem freios. Por exemplo, isso pode ser expresso no uso máximo da placa de vídeo no caso de um programa minimizado na bandeja ou independentemente de tudo e do carregamento constante de um segmento do processador. As razões para isso são intuitivas - a renda do desenvolvedor ou editor não depende da otimização (as pessoas não prestam atenção a essas coisas ao comprar), e os desenvolvedores comuns provavelmente estão mais preocupados com os KPIs locais e com a necessidade de atraso. No entanto, na prática, eu não gostaria de entrar nessa categoria.

Depois de concluir o estágio anterior no próximo lançamento com um efeito de desfoque de fundo, depois de segurar o telefone na mão por algum tempo, surgiu uma sensação de aquecimento. Se você passa mais tempo no menu, tudo fica muito pior. E mesmo que o usuário no menu presumivelmente e provavelmente não gaste muito tempo, eu não gostaria de deixar a bateria cair.

A análise mostrou que o sombreador escolhido acima tem complexidade assintótica O (r 2 ), dependendo do raio selecionado. Em outras palavras, o número de cálculos por ponto se torna 625 vezes maior se você aumentar o raio de desfoque de 1 para 25. Nesse caso, os cálculos ocorrem em cada quadro.

O primeiro passo na solução foi a ideia de que nem todos os sombreadores são igualmente úteis e o efeito de desfoque pode ser implementado de diferentes maneiras. Para fazer isso, você pode fazer um desfoque separado, cuja essência é desfocar primeiro apenas as linhas horizontalmente e depois apenas as linhas verticalmente. Como resultado, obtemos O (r) e ceteris paribus, a complexidade cai em uma ordem de magnitude. Uma maneira adicional seria usar um mipmap menor, que já ocupa parte do trabalho de desfoque. Esse shader

foi tomado como base. No entanto, seu efeito de desfoque acabou sendo insuficiente e, com alto contraste do gráfico, a imagem ficou "lascada". Para obter melhores efeitos, a distribuição foi alterada (elementos GRABPIXEL). Se no sombreador, de 0,18 a 0,05, raio 4, em nossa versão, de 0,14 a 0,03, raio 6 (ess, mas a soma de todos deve ser 1).

Assim, a complexidade do processamento foi reduzida em 1-2 ordens de magnitude. Mas isso não é necessário parar.

Parte Quatro - Fundo Estático


No entanto, se deixarmos o sombreador na imagem existente, ela estará constantemente em funcionamento, fazendo os mesmos cálculos em cada quadro. Se algo acontecer em segundo plano, precisamos fazer isso. Mas isso é completamente opcional se o fundo for estático. Assim, após o desfoque dinâmico, você pode se lembrar do resultado, colocá-lo no fundo e desligar o sombreador.

Em seguida, vamos tentar com trechos de código. A preparação da textura de destino para a tela inteira pode ficar assim:

_width = Screen.width / 2;
_height = Screen.height / 2;
_texture = new Texture2D(_width, _height);

var fillColor = Color.white;
var fillColorArray = _texture.GetPixels();
for (var i = 0; i < fillColorArray.Length; ++i)
{
	fillColorArray[i] = fillColor;
}
_texture.SetPixels(fillColorArray);
_texture.Apply();

_from.GetComponent<Image>().sprite = Sprite.Create(_texture, new Rect(0, 0, _texture.width, _texture.height), new Vector2(0.5f, 0.5f));

Ou seja, aqui é preparada uma textura 2 vezes menor horizontal e verticalmente da tela. Isso pode ser feito quase sempre, pois sabemos que o tamanho da tela dos dispositivos é múltiplo de 2 e, se feito, isso reduzirá o tamanho da textura e facilitará a desfocagem.

RenderTexture temp = RenderTexture.GetTemporary(_width, _height);
Graphics.Blit(_from_image.mainTexture, temp, _material);
RenderTexture.active = temp;

_texture.ReadPixels(new Rect(0, 0, temp.width, temp.height), 0, 0, false);
_texture.Apply();

_to_image.sprite = Sprite.Create(_texture, new Rect(0, 0, _texture.width, _texture.height), new Vector2(0.5f, 0.5f));
RenderTexture.ReleaseTemporary(temp);

Para capturar uma imagem com mainTexture usando um sombreador (no _material) Graphics.Blit é usado. Em seguida, memorizamos o resultado na textura preparada e o colocamos na imagem de destino.

Tudo ficaria bem; na prática, ele se deparou com um efeito que, dependendo do dispositivo, a imagem de fundo fica de cabeça para baixo. A razão após um estudo sobre o problema e a depuração se torna clara - a diferença nos sistemas de coordenadasDirect3D e OpenGL, que o Unity não pode ocultar. Ao mesmo tempo, nosso shader detecta os UV e os processa corretamente, e a inversão já ocorre no ambiente Unity (ReadPixels). Existem muitas dicas na rede, como "se você virou de cabeça para baixo, vire-se" ou "altere o sinal UV". O uso da macro UNITY_UV_STARTS_AT_TOP não ajudou a obter uma boa generalização para todos os dispositivos em teste. Além disso, por exemplo, encontramos um caso como esse: se você preparar a montagem para emulação no Xcode no iPhone e o Unity não usar o Metal, mas apenas o OpenGLES, será necessário interceptar esses casos, como a emulação de um dispositivo em execução em outro software.

Depois de experimentar várias opções, optei por dois tablets. O primeiro está forçando a renderização da câmera(definindo forceIntoRenderTexture no momento da captura da imagem). O segundo é a determinação do tipo de sistema gráfico em tempo real através do SystemInfo.graphicsDeviceType, que permite definir como OpenGL ou Direct3D (o primeiro grupo é OpenGLES2, OpenGLES3, OpenGLCore e Vulkan). Além disso, em nossa implementação para Direct3D-like, precisamos virar, o que é feito processualmente (por exemplo, como este ).

Conclusão


Espero que este artigo seja útil para alguém superar um rake desconhecido, melhorar a vida dos usuários e entender o Unity. Se você olhar para o efeito no uso do combate, então na Unidade parece que isso . Na aplicação, as sensações são um pouco diferentes, mas essa animação pode dar uma idéia suficiente.

All Articles