Olá! Hoje continuo a discutir . Este artigo será duas vezes maior que o anterior. Apertar! a renderização no Unity O que é um Shader? Com base no que foi descrito em um artigo anterior, um shader é um pequeno programa que pode ser usado para criar efeitos interessantes em nossos projetos. Contém cálculos matemáticos e listas de instruções (comandos). Eles nos permitem processar a cor de cada pixel na área que cobre o objeto na tela do nosso computador ou trabalhar com transformações de objetos (por exemplo, para criar grama ou água dinâmica). Este programa nos permite desenhar elementos (usando sistemas de coordenadas) com base nas propriedades de nosso objeto poligonal. Os shaders são executados na porque ela possui uma arquitetura paralela composta por milhares de núcleos pequenos e eficientes projetados para lidar com tarefas simultaneamente. A propósito, a CPU foi projetada para processamento serial sequencial. GPU Observe que existem três tipos de arquivos relacionados a sombreadores no Unity. Primeiramente, temos programas com a extensão ".shader" capazes de compilar diferentes tipos de pipelines de renderização. Em segundo lugar, temos programas com a extensão ".shadergraph" que só podem compilar para URP ou HDRP. Além disso, temos arquivos com a extensão ".hlsl" que nos permitem criar funções personalizadas. Eles são normalmente usados em um tipo de nó chamado Custom Function, que é encontrado no Shader Graph. Há também outro tipo de shader com a extensão ".cginc" - Compute Shader. Está associado ao CGPROGRAM ".shader", enquanto ".hlsl" está associado ao HLSLPROGRAM ".shadergraph". No Unity, pelo menos quatro tipos de estruturas são definidos para geração de shader. Entre eles, podemos encontrar uma combinação de shaders de vértice e fragmento, shaders de superfície para cálculo automático de iluminação e shaders de computação para conceitos mais avançados. Uma pequena excursão na linguagem shader Antes de começarmos a escrever shaders em geral, devemos levar em consideração que existem três linguagens de programação de shaders no Unity: HLSL (linguagem de sombreamento de alto nível - Microsoft) Cg (C para gráficos - NVIDIA) - um formato obsoleto ShaderLab - uma linguagem declarativa - Unity Vamos percorrer rapidamente Cg, e tocar um pouco em HLSL. ShaderLab Cg é uma linguagem de programação de alto nível projetada para compilar na maioria das GPUs. A NVIDIA o desenvolveu em colaboração com a Microsoft e usou uma sintaxe muito semelhante ao HLSL. A razão pela qual os shaders trabalham com a linguagem Cg é que eles podem compilar com HLSL e GLSL (OpenGL Shading Language), acelerando e otimizando o processo de criação de material para videogames. Todos os shaders no Unity (exceto Shader Graph e Compute) são escritos em uma linguagem declarativa chamada ShaderLab. A sintaxe dessa linguagem nos permite exibir as propriedades do shader no inspetor do Unity. Isso é muito interessante porque podemos manipular os valores de variáveis e vetores em tempo real, customizando nosso shader para obter o resultado desejado. No ShaderLab podemos definir manualmente diversas propriedades e comandos, entre eles o Fallback. É compatível com os diferentes tipos de pipelines de renderização existentes no Unity. Fallback é um bloco de código fundamental em jogos multiplataforma. Permite compilar outro shader ao invés daquele que gerou o erro. Se um sombreador quebrar durante a compilação, o Fallback retornará outro sombreador e o hardware gráfico poderá continuar seu trabalho. Isso é necessário para que não tenhamos que escrever shaders diferentes para Xbox e PlayStation, mas usar shaders unificados. Tipos básicos de shader no Unity Os tipos básicos de shader no Unity nos permitem criar sub-rotinas para serem usadas para diferentes propósitos. Vamos discutir o que cada tipo é responsável por: Este tipo de shader é caracterizado pela otimização do código de escrita que interage com o modelo de iluminação base e só funciona com RP integrado. Sombreador de superfície padrão. Shader apagado. Refere-se ao modelo de cores primárias e será a estrutura base que normalmente usamos para criar nossos efeitos. Estruturalmente, é muito semelhante ao shader Unlit. Esses shaders são usados principalmente em efeitos de pós-processamento RP integrados e requerem a função "OnRenderImage()" (C#). Shader de efeito de imagem. Este tipo é caracterizado pelo fato de ser executado na placa de vídeo e ser estruturalmente muito diferente dos shaders mencionados anteriormente. Sombreador de Computação. Um tipo experimental de shader que permite coletar e processar ray tracing em tempo real. Funciona apenas com HDRP e DXR. RayTracing Shader. Um sombreador baseado em gráfico vazio com o qual você pode trabalhar sem conhecimento de linguagens de sombreador usando nós. Gráfico de shader em branco. É um sub-shader que pode ser usado em outros shaders do Shader Graph. Subgráfico. A renderização no Unity é um tópico difícil, então leia este guia com atenção. Estrutura do sombreador Para analisar a estrutura dos shaders, basta criar um shader simples baseado no Unlit e analisá-lo. Quando criamos um shader pela primeira vez, o Unity adiciona o código padrão para facilitar o processo de compilação. No shader, podemos encontrar para que a GPU possa interpretá-los. blocos de código estruturados Se abrirmos nosso shader, sua estrutura se parecerá: Shader "Unlit/OurSampleShaderUnlit" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags {"RenderType"="Opaque"} LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; UNITY_FOG_COORDS(1) float4 vertex : SV_POSITION; }; sampler 2D _MainTex; float4 _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); UNITY_TRANSFER_FOG(o, o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); UNITY_APPLY_FOG(i.fogCoord, col); return col; } ENDCG } } } Com o exemplo atual e sua estrutura básica, fica um pouco mais claro. Um sombreador começa com um caminho no inspetor do editor do Unity (InspectorPath) e um nome (shaderName), depois propriedades (por exemplo, texturas, vetores, cores etc.) e SubShader. No final, um parâmetro Fallback opcional oferece suporte a diferentes variantes. Trabalhando com ShaderLab A maioria dos sombreadores começa declarando o sombreador e seu caminho no inspetor do Unity, bem como seu nome. Ambas as propriedades, como SubShader e Fallback, são escritas dentro do campo "Shader" na linguagem declarativa ShaderLab. Shader "OurPath/shaderName" { // The shader code will be here } Tanto o caminho quanto o nome do sombreador podem ser alterados conforme necessário no projeto. As propriedades do sombreador correspondem a uma lista de parâmetros que podem ser manipulados no inspetor do Unity. Existem oito propriedades diferentes, tanto em termos de valor quanto de utilidade. Usamos essas propriedades em relação ao shader que queremos criar ou modificar, dinamicamente ou em tempo de execução. A sintaxe para declarar uma propriedade é a seguinte: PropertyName ("display name", type) = defaultValue "PropertyName" representa o nome da propriedade (por exemplo, _MainTex), "nome de exibição" especifica o nome da propriedade no inspetor do Unity (por exemplo, Textura), "tipo" indica seu tipo (por exemplo, Cor, Vetor, 2D, etc.). Finalmente, "defaultValue" é o valor padrão atribuído à propriedade (por exemplo, se a propriedade for "Color", podemos defini-la como branco (1, 1, 1, 1, 1, 1). O segundo componente de um shader é o SubShader. Cada shader consiste em pelo menos um SubShader para carregamento perfeito. Quando houver mais de um SubShader, o Unity processará cada um deles e selecionará o mais adequado de acordo com as especificações do hardware, começando pelo primeiro e terminando no último da lista (por exemplo, para separar o shader para iOS e Android). Quando o SubShader não é suportado, o Unity tentará usar o componente Fallback correspondente ao shader padrão para que o hardware possa continuar sua tarefa sem erros gráficos. Shader "OurPack/OurShader" { Properties { ... } SubShader { // Here will be the shader configuration } } Você pode ler mais sobre parâmetros e sub-shaders e . aqui aqui Misturando Precisamos de mesclagem para o processo de combinar dois pixels em um. A mesclagem é suportada tanto no Built-In quanto no SRP. A mesclagem ocorre na etapa que combina a cor final de um pixel com sua profundidade. Esse estágio ocorre no final do pipeline de renderização após o estágio do sombreador de fragmento ao executar o buffer de estêncil, buffer z e mistura de cores. Por padrão, esta propriedade não está escrita no shader, pois é um recurso opcional e é usado principalmente ao trabalhar com objetos transparentes. Por exemplo, é usado quando precisamos desenhar um pixel com um pixel de baixa opacidade na frente do outro (isso é muito usado em UI). Podemos habilitar a mistura aqui: Blend [SourceFactor] [DestinationFactor] Você pode ler mais sobre a combinação . aqui Buffer Z (Buffer de Profundidade) Para entender ambos os conceitos, devemos primeiro aprender como funcionam o Z-Buffer (também conhecido como Depth Buffer) e o teste de profundidade. Antes de começarmos, devemos considerar que os pixels possuem valores de profundidade. Esses valores são armazenados no Depth-Buffer, que determina se um objeto vai na frente ou atrás de outro objeto na tela. O teste de profundidade, por outro lado, é uma condição que determina se um pixel será atualizado ou não no Depth-Buffer. Como já sabemos, um pixel tem um valor atribuído que é medido em cores RGB e armazenado no buffer de cores. Z-buffer adiciona um valor adicional que mede a profundidade do pixel em termos de distância da câmera, mas apenas para as superfícies que estão dentro de sua área frontal. Isso permite que 2 pixels sejam iguais em cor, mas diferentes em profundidade. Quanto mais próximo o objeto estiver da câmera, menor será o valor do Z-buffer, e os pixels com valores de buffer menores substituem os pixels com valores maiores. Para entender o conceito, suponha que temos uma câmera e algumas primitivas em nossa cena, e todas elas estão localizadas no eixo espacial "Z". A palavra "buffer" refere-se ao "espaço de memória" onde os dados serão armazenados temporariamente, portanto, o Z-buffer refere-se aos valores de profundidade entre os objetos em nossa cena e a câmera que são atribuídos a cada pixel. Podemos controlar o teste de profundidade graças aos parâmetros ZTest no Unity. Seleção Essa propriedade, compatível com RP integrado e URP/HDRP, controla quais faces do polígono serão removidas ao processar a profundidade de pixel. O que isto significa? Lembre-se de que um objeto polígono tem bordas internas e bordas externas. Por padrão, as bordas externas são visíveis (CullBack). No entanto, podemos ativar as bordas internas: Ambas as arestas do objeto são renderizadas Abater. Por padrão, as bordas traseiras do objeto são exibidas Recolha. As bordas frontais do objeto são renderizadas. Frente de Abate. Este comando tem três valores, ou seja, Back, Front e Off. O comando Voltar está ativo por padrão; no entanto, geralmente, a linha de código associada ao abate não é visível no sombreador para fins de otimização. Se quisermos alterar os parâmetros, devemos adicionar a palavra "Cull" seguida do modo que queremos usar. Shader "Culling/OurShader" { Properties { [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 } SubShader { // Cull Front // Cull Off Cull [_Cull] } } Também podemos configurar dinamicamente por meio da dependência "UnityEngine.Rendering.CullMode". É Enum e é passado como um argumento para uma função. os parâmetros Culling no inspetor Unity Usando Cg/HLSL Em nosso shader, podemos encontrar pelo menos três variantes de diretivas padrão. Essas são diretivas de processador incluídas em Cg ou HLSL. Sua função é ajudar nosso shader a reconhecer e compilar certas funções que de outra forma não seriam reconhecidas como tal: Ele permite que o estágio do sombreador de vértice seja compilado na GPU como um sombreador de vértice. #pragma vertex vert. Esta diretiva executa a mesma função que pragma vertex, com a diferença que permite que um fragment shader stage chamado "frag" seja compilado como um fragment shader no código #pragma fragmento frag. Ao contrário das diretivas anteriores, tem uma dupla função. Primeiro, multi_compile refere-se a um shader variante que nos permite gerar variantes com diferentes funcionalidades em nosso shader. Em segundo lugar, a palavra "_fog" inclui a funcionalidade de névoa da janela Lighting no Unity. Se formos ao ambiente/outras configurações, podemos ativar ou desativar as opções de névoa do nosso shader. #pragma multi_compile_fog. Também podemos conectar arquivos Cg/HLSL em nosso shader. Normalmente fazemos isso quando conectamos UnityCG.cginc. Inclui coordenadas de névoa, posições de objetos para recorte, transformações de textura, transporte de névoa e muito mais, incluindo constantes UNITY_PI. A coisa mais importante que podemos fazer com Cg/HLSL é escrever funções de processamento direto para shaders de vértice e fragmento, usar variáveis dessas linguagens e várias coordenadas como coordenadas de textura (TEXCOORD0). #pragma vertex vert #pragma fragment frag v2f vert (appdata v) { // Ability to work with the vertex shader } fixed4 frag (v2f i) : SV_Target { // Ability to work with fragment shader } Você pode ler mais sobre Cg/HLSL . aqui gráfico de sombreamento Shader Graph é uma nova solução para Unity que permite que você crie suas soluções sem conhecimento da linguagem shader. Nós visuais são usados para trabalhar com ele (mas ninguém proíbe combiná-los com a linguagem shader). Shader Graph funciona apenas com HDRP e URP. Você deve se lembrar que ao trabalhar com Shader Graph, as versões desenvolvidas para Unity 2018 são versões BETA e não recebem suporte. As versões desenvolvidas para Unity 2019.1+ são ativamente compatíveis e recebem suporte. Outra questão é que é muito provável que os shaders criados com esta interface não sejam compilados corretamente em diferentes versões. Isso ocorre porque novos recursos são adicionados a cada atualização. Então, o Shader Graph é uma boa ferramenta para o desenvolvimento de shaders? Claro que é. E pode ser tratado não apenas por um programador gráfico, mas também por um designer técnico ou artista. Para criar um gráfico, tudo o que precisamos fazer é selecionar o tipo que queremos no editor do Unity. Antes de começarmos, vamos apresentar brevemente o sombreador de vértice/fragmento no nível do gráfico de sombreamento. Como podemos ver, existem três pontos de entrada definidos no estágio de shader de vértice, a saber: Position(3), Normal(3) e Tangent(3), assim como em um shader Cg ou HLSL. Quando comparado a um shader regular, isso significa que Position(3) = POSITION[n], Normal(3) = NORMAL[n] e Tangent(3) = TANGENT[n]. Por que um Shader Graph tem três dimensões, mas um Cg ou HLSL tem 4? Lembre-se de que as quatro dimensões de um vetor correspondem ao seu componente W, que é "um ou zero" na maioria dos casos. Quando W = 1, o vetor corresponde a um espaço ou posição de ponto. Considerando que quando W = 0, o vetor corresponde a uma direção no espaço. Então, para configurar nosso shader, primeiro vamos ao editor e criamos dois parâmetros: color - _Color e Texture2D - _MainTex. Para criar um link entre as propriedades do ShaderLab e nosso programa, devemos criar variáveis no campo CGPROGRAM. No entanto, esse processo é diferente no Shader Graph. Devemos arrastar e soltar as propriedades que queremos usar no espaço de trabalho do nó. Tudo o que precisamos fazer para fazer o Texture2D funcionar em conjunto com o nó Sample Texture 2D é conectar a saída da propriedade _MainTex à entrada Texture(T2). Para multiplicar os dois nós (cor e textura), basta chamar o nó Multiply e passar os dois valores como ponto de entrada. Por fim, precisamos enviar a saída de cor na cor base no estágio de sombreamento de fragmento. Agora salve o shader e pronto. Nosso primeiro shader está pronto. Também podemos passar para a configuração geral do gráfico, dividida em seções de Nó e Gráfico. Eles têm propriedades personalizáveis que nos permitem alterar a reprodução de cores. Podemos encontrar opções de mesclagem, recorte alfa, etc. Além disso, podemos personalizar as propriedades dos nós em nossa configuração Shader Graph. Os próprios nós fornecem análogos de certas funções que escrevemos no ShaderLab. Como exemplo, o código da função Clamp: void Unity_Clamp_float4(float4 In, float4 Min, float4 Max, out float4 Out) { Out = clamp(In, Min, Max); } Desta forma, podemos simplificar nossas vidas e reduzir o tempo para escrever shaders em detrimento de gráficos visuais. Conclusão Posso falar muito sobre shaders e por muito tempo, bem como tocar no próprio processo de renderização. Discuti todos os fundamentos neste . Aqui eu não discuti os shaders de raytracing e Compute-Shading. Cobri as linguagens de shader superficialmente e descrevi os processos apenas a partir da ponta do iceberg. guia sobre renderização no Unity