Olá pessoal! Meu nome é Grigory Dyadichenko e sou o fundador e CTO da Foxsys Studios. Hoje vamos falar sobre shaders de vértice. O artigo examinará a prática do ponto de vista do Unity, exemplos muito simples, bem como muitos links para o estudo de informações sobre shaders no Unity. Se você é bom em escrever shaders, não encontrará nada novo para si. Quem quer começar a escrever shaders no Unity, bem-vindo ao gato.

Pouco de teoria
Para uma melhor compreensão do processo de sombreador, vamos dar uma olhada em um pouco de teoria. Um shader de vértice ou shader de vértice é um estágio programável de um shader que trabalha com vértices individuais. Os vértices, por sua vez, armazenam vários atributos que são processados por essa parte do shader para obter atributos convertidos na saída.
Exemplos onde shaders de vértice são usados
Deformação de objetos - ondas realistas, o efeito de ondulações da chuva, deformação quando uma bala atinge, tudo isso pode ser feito com sombreadores de vértice e parecerá mais realista do que a mesma coisa feita através do Bump Mapping na parte fragmentada do shader. Uma vez que esta é uma mudança na geometria. Os shaders de nível 3.0 sobre esse assunto têm uma técnica chamada Mapeamento de posicionamento, pois agora eles têm acesso a texturas na parte do vértice do shader.
Animação de objetos. Os jogos parecem mais animados e interessantes quando as plantas reagem a um personagem ou as árvores balançam ao vento. Para isso, também são utilizados shaders de vértice.
Desenho de iluminação ou estilizado. Em muitos jogos, do ponto de vista do estilo, não é a iluminação pbr que parece muito mais interessante, mas a estilização. Ao mesmo tempo, não faz sentido calcular nada na parte do fragmento.
Skinning. No momento determinado nos mecanismos de jogo, esse problema está resolvido, mas, no entanto, é útil entender os sombreadores de vértices para entender como ele funciona.
Exemplos simples de trabalho com vértices

Eu não quero que isso aconteça, como nas lições antigas sobre como desenhar uma coruja, então vamos por etapas. Crie um shader de superfície padrão. Isso pode ser feito com o botão direito do mouse na Project View ou no painel superior da guia Assets. Crie-> Shader-> Shader de superfície padrão.
E temos um padrão em branco.
Sombreador de superfícieShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
sampler2D _MainTex;
struct Input
{
float2 uv_MainTex;
};
half _Glossiness;
half _Metallic;
fixed4 _Color;
// Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
// See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
// #pragma instancing_options assumeuniformscaling
UNITY_INSTANCING_BUFFER_START(Props)
// put more per-instance properties here
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
// Albedo comes from a texture tinted by color
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = ca;
}
ENDCG
}
FallBack "Diffuse"
}
Como ele funciona e, em geral, o analisaremos em detalhes no artigo após a prática básica, além de o entendermos parcialmente durante a implementação dos shaders. Por enquanto, deixe algumas das coisas permanecerem como dadas. Em resumo, não há mágica (em termos de como os parâmetros são conectados etc.) Apenas para determinadas palavras-chave, a unidade gera código para você, para não escrevê-lo do zero. Portanto, esse processo não é óbvio o suficiente. Você pode ler mais sobre o sombreador de superfície e suas propriedades no Unity aqui.
docs.unity3d.com/Manual/SL-SurfaceShaders.htmlRemoveremos tudo supérfluo dele para que não distraia, pois, no momento, não é necessário. E obtenha um shader tão curto.
Shader simplificadoShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}

Apenas a cor no modelo com iluminação. Nesse caso, a Unity é responsável pelo cálculo da iluminação.
Primeiro, adicione o efeito mais simples dos exemplos do Unity. A extrusão é normal e, no seu exemplo, analisaremos como ela funciona.
Para fazer isso, adicione o modificador
vertex: vert à linha
#pragma surface surf Standard fullforwardshadows line. Se passarmos
inout appdata_full v como parâmetro para uma função, então, em essência, essa função é um modificador de vértice. Na sua essência, faz parte do sombreador de vértices, criado pela unidade geradora de código, que executa o processamento preliminar dos vértices. Também no bloco
Propriedades , adicione o campo
_Amount aceitando valores de 0 a 1. Para usar o campo
_Amount no shader, também precisamos defini-lo lá. Na função, simplesmente
mudaremos para o normal, dependendo de
_Amount , onde 0 é a posição padrão do vértice (deslocamento zero) e 1 é o deslocamento exatamente para o normal.
SimpleVertexExtrusionShaderShader "Custom/SimpleVertexExtrusionShader"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amount ("Extrusion Amount", Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amount;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amount;
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Você pode perceber uma característica importante dos shaders. Embora o sombreador seja executado a cada quadro, o resultado obtido durante a operação do sombreador não é armazenado na malha, mas é usado apenas para renderização. Portanto, é impossível se relacionar com as funções do sombreador, bem como com a
atualização nos scripts. Eles são aplicados a todos os quadros sem alterar os dados da malha, mas simplesmente modificando a malha para renderização adicional.
Por exemplo, uma das maneiras mais fáceis de criar uma animação é usar o tempo para alterar a amplitude. A unidade possui variáveis
internas , uma lista completa das quais podem ser encontradas aqui
docs.unity3d.com/Manual/SL-UnityShaderVariables.html Nesse caso, escreveremos um novo sombreador com base no nosso sombreador anterior. Em vez de
_Amount, vamos criar o valor flutuante
_Amplitude e usar a variável
interna do Unity
_SinTime .
_SinTime é o seno do tempo e, portanto, leva valores de -1 a 1. No entanto, não esqueça que todas as variáveis de tempo
internas nos shaders de unidade são vetores
float4 . Por exemplo,
_SinTime é definido como
(sin (t / 8), sin (t / 4), sin (t / 2), sin (t)) , onde t é a hora. Portanto, pegamos o componente z para que a animação seja mais rápida. E temos:
SimpleVertexExtrusionWithTimeShader "Custom/SimpleVertexExtrusionWithTime"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Amplitude ("Extrusion Amplitude", float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex:vert
#pragma target 3.0
struct Input
{
float4 color : COLOR;
};
fixed4 _Color;
float _Amplitude;
void vert (inout appdata_full v)
{
v.vertex.xyz += v.normal * _Amplitude * (1 - _SinTime.z);
}
void surf (Input IN, inout SurfaceOutputStandard o)
{
fixed4 c = _Color;
o.Albedo = c.rgb;
}
ENDCG
}
FallBack "Diffuse"
}
Então, esses eram exemplos simples. É hora de desenhar uma coruja!
Deformação de objetos
Eu já escrevi um artigo inteiro sobre o assunto de um efeito de deformação com uma análise detalhada da matemática do processo e da lógica do pensamento ao desenvolver esse efeito
habr.com/en/post/435828 Esta será a nossa coruja.
Todos os sombreadores do artigo estão escritos em hlsl. Na verdade, essa linguagem possui sua própria documentação volumosa, que muitos esquecem e se perguntam de onde vêm metade das funções com fio, embora estejam definidas no HLSL
docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl- funções intrínsecasMas, de fato, os shaders de superfície em uma unidade são um tópico grande e volumoso por si só. Além disso, você nem sempre quer mexer com a iluminação do Unity. Às vezes, você precisa trapacear e escrever o shader mais rápido que possui apenas o conjunto certo de efeitos predefinidos. Na unidade, você pode escrever shaders de nível inferior.
Shaders de baixo nível

De acordo com a boa e velha tradição de trabalhar com shaders, a seguir atormentaremos o coelho de Stanford.
Em geral, o chamado Unity ShaderLab é essencialmente uma visualização de um inspetor com campos de materiais e alguma simplificação dos shaders de escrita.
Veja a estrutura geral do shaderlab shader:
Estrutura geral do shaderShader "MyShaderName"
{
Properties
{
//
}
SubShader // ( )
{
Pass
{
//
}
//
}
//
FallBack "VertexLit" // ,
}
Diretivas de compilação, como
#pragma vertex vertfrag de fragmento #pragmadetermine qual shader funciona para compilar como shaders de vértice e fragmento, respectivamente.
Digamos que tomemos um dos exemplos mais comuns - um sombreador para exibir a cor dos normais:
SimpleNormalVisualizationShader "Custom/SimpleNormalVisualization"
{
Properties
{
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.color = v.normal * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return fixed4 (i.color, 1);
}
ENDCG
}
}
FallBack "VertexLit"
}

Nesse caso, na parte do vértice, escrevemos o valor normal convertido na cor do vértice e na parte do pixel usamos essa cor como a cor do modelo.
A função
UnityObjectToClipPos é uma função auxiliar do Unity (do arquivo
UnityCG.cginc ) que converte os vértices do objeto na posição associada à câmera. Sem ele, um objeto, quando entrar na visibilidade da câmera (frustrum), será desenhado nas coordenadas da tela, independentemente da posição da transformação. Como inicialmente as posições dos vértices são apresentadas nas coordenadas do objeto. Apenas valores relativos ao seu pivô.
Este bloco.
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR0;
};
Essa é a definição da estrutura que será processada na parte do vértice e transferida para a parte do fragmento. Nesse caso, é determinado que dois parâmetros são obtidos da malha - a posição do vértice e a cor do vértice. Você pode ler mais sobre quais dados podem ser lançados em uma unidade neste link
docs.unity3d.com/Manual/SL-VertexProgramInputs.htmlEsclarecimentos importantes. Os nomes dos atributos da malha não importam. Ou seja, digamos que no atributo de cor, você pode escrever o vetor de desvio da posição original (dessa maneira, às vezes, eles produzem um efeito quando o personagem está andando, de modo que a grama "repele"). Como esse atributo será processado depende inteiramente do seu sombreador.
Conclusão
Obrigado pela atenção! É problemático escrever alguns efeitos complexos sem uma parte fragmentária; por esse motivo, discutiremos artigos semelhantes em artigos separados. Espero que, durante este artigo, tenha ficado um pouco mais claro como o código dos shaders de vértice é escrito em geral e onde você pode encontrar informações para estudo, uma vez que os shaders são um tópico muito profundo.
Em artigos futuros, analisaremos os outros tipos de shaders, efeitos individuais, e tentarei descrever minha lógica de pensamento ao criar efeitos novos ou complexos.
Também foi criado um repositório onde todos os resultados desta série de artigos serão adicionados ao
github.com/Nox7atra/ShaderExamples.Espero que essas informações sejam úteis para iniciantes que estão iniciando sua jornada no estudo deste tópico.
Alguns links úteis (incluindo fontes):
www.khronos.org/opengl/wiki/Vertex_Shaderdocs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-referencedocs.unity3d.com/en/current/Manual/SL-Reference.htmldocs.unity3d.com/Manual/GraphicsTutorials.htmlwww.malbred.com/3d-grafika-3d-redaktory/sovremennaya-terminologiya-3d-grafiki/vertex-shader-vershinnyy-sheyder.html3dpapa.ru/accurate-displacement-workflow