Iluminação personalizada no Shader Graph: Expandindo seus gráficos em 2019

Com o lançamento do Unity Editor 2019.1, o pacote Shader Graph saiu oficialmente da versão de pré-visualização! Agora, na versão 2019.2, estamos trazendo ainda mais recursos e funcionalidades ao Shader Graph.
Para manter código personalizado dentro do seu Shader Graph, agora você pode usar nosso novo nó de Função Personalizada. Este nó permite que você defina suas próprias entradas e saídas personalizadas, reordene-as e injete funções personalizadas diretamente no próprio nó ou referenciando um arquivo externo.
Os subgráficos também receberam uma atualização: agora você pode definir suas próprias saídas para subgráficos, com diferentes tipos, nomes personalizados e portas reordenáveis. Além disso, o Blackboard para Subgráficos agora oferece suporte a todos os tipos de dados suportados pelo gráfico principal.
Usar o Shader Graph para criar shaders poderosos e otimizados ficou um pouco mais fácil. Na versão 2019.2, agora você pode definir manualmente a precisão dos cálculos no seu gráfico, seja em todo o gráfico ou por nó. Nossos novos Modos de Cor tornam rápido e fácil visualizar o fluxo de Precisão, a categoria de nós ou exibir cores personalizadas para seu próprio uso!
Consulte a documentação do Shader Graph para obter mais informações sobre esses novos recursos.
Para ajudar você a começar a usar o novo fluxo de trabalho de função personalizada, criamos um projeto de exemplo junto com instruções passo a passo. Baixe o projeto do nosso repositório e acompanhe! Este projeto mostrará como usar o nó Função Personalizada para escrever shaders de iluminação personalizados para o Lightweight Render Pipeline (LWRP). Se você quiser continuar usando um projeto novo, certifique-se de usar o Editor 2019.2 e o pacote LWRP versão 6.9.1 ou superior.
Para começar, precisamos obter informações da luz principal em nossa cena. Comece selecionando Criar > Shader > Gráfico não iluminado para criar um novo Gráfico não iluminado de sombreamento. No menu Criar Nó, localize o novo nó Função Personalizada e clique no ícone de engrenagem no canto superior direito para abrir o menu do nó.
Neste menu, você pode adicionar entradas e saídas. Adicione duas portas de saída para Direção e Core selecione Vetor 3 para ambas. Se você vir um sinalizador de erro “identificador não declarado”, não se preocupe; ele desaparecerá quando começarmos a adicionar nosso código. No menu suspenso Tipo , selecione String. Atualize o nome da sua função — neste exemplo, estamos usando “MainLight”. Agora, podemos começar a adicionar nosso código personalizado na caixa de texto.

Primeiro, vamos usar um sinalizador chamado `#ifdef SHADERGRAPH_PREVIEW`. Como as caixas de visualização nos nós não têm acesso a dados leves, precisamos informar ao nó o que exibir nas caixas de visualização no gráfico. `#ifdef` diz ao compilador para usar código diferente em situações diferentes. Comece definindo seus valores de fallback para as portas de saída.
Em seguida, usaremos `#else` para dizer ao compilador o que fazer quando não estiver em uma visualização. É aqui que realmente obtemos nossos dados de luz. Use a função interna `GetMainLight()` do pacote LWRP. Podemos usar essas informações para atribuir as saídas de direção e cor . Sua função personalizada agora deve ficar assim:
Agora, é uma boa ideia adicionar esse nó a um grupo para que você possa marcar o que ele está fazendo. Clique com o botão direito do mouse no nó, selecione Criar grupo a partir da seleçãoe renomeie o título do grupo para descrever o que seu nó está fazendo. Aqui entramos em “Obter luz principal”.

Agora que temos nossos dados de luz, podemos calcular algum sombreamento. Vamos começar com uma iluminação lambertiana padrão, então vamos pegar o produto escalar do vetor normal mundial e a direção da luz. Passe-o para um nó Saturate e multiplique-o pela cor clara. Conecte isso à porta Color do nó Unlit Master e sua visualização deverá ser atualizada com algum sombreamento personalizado!

Agora que sabemos como obter dados de luz usando o nó Função Personalizada, podemos expandir nossa função. Nossa próxima função obtém valores de atenuação da luz principal, além da direção e da cor. Como esta é uma função mais complicada, vamos mudar para o modo de arquivo e usar um arquivo de inclusão HLSL. Isso permite que você crie funções mais complicadas em um editor de código adequado antes de injetá-las no gráfico. Isso também significa que temos um local unificado para depurar o código. Comece abrindo o arquivo de inclusão `CustomLighting` na pasta Assets > Include do projeto. Por enquanto, vamos nos concentrar apenas na função `MainLight_half`. A função se parece com isso:
Esta função inclui alguns novos dados de entrada e saída, então vamos voltar ao nosso nó Função Personalizada e adicioná-los. Adicione duas novas saídas para DistanceAtten (atenuação de distância) e ShadowAtten (atenuação de sombra). Em seguida, adicione a nova entrada para WorldPos (posição mundial). Agora que temos nossas entradas e saídas, podemos referenciar o arquivo de inclusão. Altere o menu suspenso Tipo para Arquivo. Na entrada Origem, navegue até o arquivo de inclusão e selecione o Ativo para referência. Agora, precisamos informar ao nó qual função usar. Na caixa Nome , inserimos “MainLight”.

Você notará que o arquivo de inclusão tem `_half` no final do nome da função, mas nossa opção de nome não. Isso ocorre porque o compilador Shader Graph acrescenta o formato de precisão a cada nome de função. Como estamos definindo nossa própria função, precisamos do código-fonte para informar ao compilador qual formato de precisão nossa função usa. No nó, no entanto, precisamos apenas referenciar o nome da função principal. Você pode criar uma duplicata da função que usa valores 'float' para compilar no modo de precisão float. O modo de cor 'Precisão' permite que você rastreie facilmente o conjunto de precisão para cada nó no gráfico, com azul representando flutuante e vermelho representando metade.
Provavelmente desejaremos usar essa função novamente em outro lugar, e a maneira mais fácil de tornar essa Função Personalizada reutilizável é envolvê-la em um Subgráfico. Selecione o nó e seu grupo e clique com o botão direito para encontrar Converter em subgráfico. Nós chamamos o nosso de “Get Main Light”. No Sub Graph, basta adicionar as portas de saída necessárias ao nó de saída do Sub Graph e conectar a saída do nó à saída do Sub Graph. Em seguida, adicionaremos um nó de posição mundial para conectar à entrada.

Salve o Subgráfico e volte para o nosso gráfico apagado. Vamos adicionar dois novos nós de multiplicação à nossa lógica existente. Primeiro, multiplique as duas saídas de atenuação. Depois, multiplique essa saída pela cor da luz. Podemos multiplicar isso pelo NdotL anterior para calcular corretamente a atenuação em nosso sombreamento básico.

O shader que criamos é ótimo para objetos foscos, mas e se quisermos um pouco de brilho? Podemos adicionar nossos próprios cálculos especulares ao nosso shader! Para esta etapa, usaremos outro nó de Função Personalizada encapsulado em um Subgráfico, chamado Direct Specular. Dê uma olhada no arquivo de inclusão `CustomLighting` novamente e veja que agora estamos referenciando outra função do mesmo arquivo:
Esta função realiza alguns cálculos especulares simples e, se você estiver curioso, pode ler mais sobre eles aqui. O Subgráfico para esta função também inclui algumas entradas no Blackboard:

Certifique-se de que seu novo nó tenha todas as portas de entrada e saída apropriadas para corresponder à função. Adicionar propriedades ao Blackboard é simples; basta clicar no ícone Adicionar (+) no canto superior direito e selecionar o tipo de dados. Clique duas vezes na pílula para renomear a entrada e arraste e solte a pílula para adicioná-la ao gráfico. Por fim, atualize a porta de saída do seu Sub Graph e salve-a.
Agora que o cálculo especular está configurado, podemos voltar ao gráfico apagado e adicioná-lo por meio do menu Criar Nó. Conecte a saída de atenuação à entrada de cor do subgráfico especular direto . Em seguida, conecte a saída Direction da função Get Main Light à entrada Direction do Sub Graph especular. Adicione o resultado de NdotL*Attenuation à saída do Direct Specular Sub Graph e insira-o na saída Color .

Agora temos um pouco de brilho!
A luz principal do LWRP se refere à luz direcional mais brilhante em relação ao objeto, que geralmente é o sol. Para melhorar o desempenho em hardware de baixo custo, o LWRP calcula a luz principal e quaisquer luzes adicionais separadamente. Para garantir que nosso shader calcule corretamente todas as luzes na cena, e não apenas a luz direcional mais brilhante, você precisa criar um loop em sua função. Para obter dados de luz adicionais, usamos um novo Subgráfico para encapsular um novo nó de Função Personalizada. Dê uma olhada na função `AdditionalLight_float` no arquivo de inclusão `CustomLighting`:
Como antes, use a função `AdditionalLights` na referência de arquivo do nó Função Personalizada e certifique-se de ter criado todas as entradas e saídas adequadas. Certifique-se de expor a Cor Especular e a Suavidade Especular no Quadro Negro do Subgráfico no qual o nó está encapsulado. Use os nós Posição, Vetor Normal e Direção da Visualização para conectar a Posição Mundial, a Normal Mundiale a Direção da Visualização do Espaço Mundial no Subgráfico.
Depois de configurar a função, use-a! Primeiro, pegue seu gráfico principal Unlit da etapa anterior e recolha-o em um Subgráfico. Selecione os nós e clique com o botão direito em Converter em subgráfico. Remova o último nó Add e conecte as saídas nas portas de saída do Sub Graph. Recomendamos que você também crie propriedades de entrada para Especular e Suavidade.

Agora você pode combinar seus cálculos de luz principais e seus cálculos de luz adicionais. No gráfico principal Unlit, crie um novo nó para os cálculos de Luz Adicional para acompanhar os cálculos de Luz Principal. Adicione as saídas Difusa e Especular da Luz Principal e das Luzes Adicionais. Bem simples!


Agora você sabe como obter os dados de todas as luzes em uma cena para um projeto LWRP, mas o que pode fazer com eles? Um dos usos mais comuns para iluminação personalizada em shaders é o shader de desenho animado clássico!
Com todos os dados de luz, criar um shader de desenho animado é bem simples. Primeiro, pegue todos os cálculos de luz que você fez até agora e envolva-os em um Subgráfico mais uma vez. Isso ajudará na legibilidade do shader final. Não se esqueça de remover o nó Add final e alimentar Diffuse e Specular em portas de saída separadas no nó de saída Sub Graph.
Existem muitos métodos para criar sombreamento de desenho animado, mas neste exemplo, usaremos a intensidade da luz para procurar cores em uma Textura de Rampa. Essa técnica é geralmente chamada de Iluminação de Rampa. Incluímos alguns exemplos do tipo de ativo de textura necessário para a iluminação de rampa no projeto de amostra. Você também pode experimentar um gradiente para usar rampas dinâmicas na Iluminação de Rampa. O primeiro passo é converter a intensidade de Difuso e Especular de valores RGB para valores HSV. Isso nos permite usar a intensidade da cor da luz (os valores HSV) para determinar o brilho no shader e nos ajuda a amostrar a textura em diferentes pontos ao longo do eixo horizontal do ativo. Use um valor estático para o canal Y do UV para determinar, de cima para baixo, qual parte da imagem deve ser amostrada. Você pode usar esse valor estático como um índice para referenciar várias rampas de iluminação para o projeto em um único ativo de textura.

Depois de definir os valores de UV, use um nó LOD de textura de amostra 2D para amostrar a textura de rampa. O LOD de amostra é importante; se usarmos um nó de textura de amostra 2D regular, a rampa será automaticamente mixada em uma cena, e objetos mais distantes terão comportamentos de iluminação diferentes. Usar um nó LOD 2D de textura de amostra nos permite determinar manualmente o nível mip. Além disso, como a textura Ramp tem apenas 2 pixels de altura, criamos nosso próprio estado de amostragem para as texturas. Para garantir que a textura seja amostrada corretamente, definimos o filtro como ponto e o envoltório como grampo. Expomos isso como uma propriedade no Blackboard para que você possa alterar as configurações caso o recurso de textura seja alterado.

Por fim, multiplicamos a amostra de rampa dos cálculos difusos por uma propriedade de cor, Diffuse, para que possamos alterar as cores do objeto. Adicione a amostra de rampa dos cálculos especulares à saída Diffuse e conecte a cor final ao nó Master.


Esta configuração simples de iluminação personalizada pode ser expandida e aplicada a uma ampla variedade de casos de uso em todos os tipos de cenas. Em nosso projeto de exemplo, incluímos uma cena completa configurada com shaders que usam nossa configuração de iluminação personalizada. Ele também contém animação de vértice, uma aproximação simples de espalhamento de subsuperfície, bem como refrações e colorações que usam profundidade. Baixe o projeto e confira nossos recursos de exemplo para explorar métodos mais avançados!
Se você quiser discutir o Shader Graph e os shaders que você pode fazer com ele, venha participar do nosso novo fórum! Você também pode encontrar membros da comunidade e (às vezes) alguns desenvolvedores no Discord da comunidade!
Não se esqueça de ficar de olho nas gravações das nossas sessões do SIGGRAPH 2019, onde entraremos em mais detalhes sobre o uso do Shader Graph para iluminação personalizada!