Exploração do shader de areia do jogo Journey

Começo de uma série de artigos aqui

imagem

Parte 4: Imagem em Espelho


Nesta parte, focaremos nos reflexos do espelho, graças aos quais as dunas se assemelham a um oceano de areia.

Um dos efeitos mais intrigantes da renderização de areia do Journey é como as dunas brilham nos raios de luz. Essa reflexão é chamada especular . O nome vem da palavra latina speculum , que significa "espelho" . A reflexão especular é um conceito abrangente que combina todos os tipos de interações nas quais a luz é fortemente refletida em uma direção, em vez de dispersa ou absorvida. Graças a reflexões especulares, a água e as superfícies polidas em um determinado ângulo parecem brilhantes.

Em ViagemExistem três tipos de reflexos no espelho: iluminação da borda , especular do oceano e reflexos de brilho , mostrados no diagrama abaixo. Nesta parte, examinaremos os dois primeiros tipos.




Antes e depois de aplicar reflexos no espelho

Iluminação da borda


Você pode perceber que, em cada nível de viagem , é apresentado um conjunto limitado de cores. E enquanto isso cria uma estética forte e limpa, essa abordagem complica a renderização da areia. As dunas são renderizadas apenas por um número limitado de tons, por isso é difícil para o jogador entender onde uma termina a longa distância e a outra começa.

Para compensar isso, a borda de cada duna tem um leve efeito de brilho, destacando seus contornos. Isso permite que as dunas não se escondam no horizonte e cria a ilusão de um ambiente muito mais amplo e complexo.

Antes de começar a descobrir como implementar esse efeito, vamos expandir a função de iluminação adicionando cores difusas a ela (considerado por nós na parte anterior do artigo) e um novo componente generalizado da reflexão especular.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor (N, L);
    float3 rimColor     = RimLighting  (N, V);

    // Combining
    float3 color = diffuseColor + rimColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

No trecho de código mostrado acima, vemos que o componente espelho da iluminação do aro, chamado de rimColor, é simplesmente adicionado à cor difusa original.

Efeitos de alta faixa dinâmica e bloom
, — RGB 01. . , 1.

, , 01. , , 1. High Dynamic Range, 1«» . bloom, . .

Reflexões de Fresnel


O brilho das bordas pode ser realizado de várias maneiras diferentes. A codificação de shader mais popular usa o conhecido modelo de reflexão de Fresnel .

Para entender a equação subjacente à reflexão de Fresnel, é útil visualizar onde ela ocorre. O diagrama abaixo mostra como a duna é visível através da câmera (em azul). A seta vermelha indica a superfície normal do topo da duna, onde deve ser uma imagem espelhada. É fácil ver que todas as margens da duna têm uma propriedade comum: a normal (N , vermelho) perpendicular àdireção da visão(V , azul).


Semelhante ao que fizemos na parte sobre Diffuse Color, você pode usar o produto escalar NeV para obter uma medida do seu paralelismo. Nesse casoNV é igual0 , porque dois vetores unitários são perpendiculares; em vez disso, podemos usar1NV para obter uma medida do seu não paralelismo.

Uso direto1NV não nos dará bons resultados, porque o reflexo será muito forte. Se queremos tornar a reflexãomais nítida, podemos apenas levar a expressão em grau. Grau de magnitude de0 a1 permanece limitado a um intervalo, mas a transição entre escuridão e luz se torna mais nítida.

O modelo de reflexão de Fresnel afirma que o brilho da luzI é definido da seguinte forma:

I=(1NV)powerstrength(1)


Onde powerestrength são dois parâmetros que podem ser usados ​​para controlar o contraste e a força do efeito. Parâmetrospower estrength , por vezes, chamadoespecularebrilho, mas os nomes podem variar.

A equação (1) é muito fácil de converter em código:

float _TerrainRimPower;
float _TerrainRimStrength;
float3 _TerrainRimColor;

float3 RimLighting(float3 N, float3 V)
{
    float rim = 1.0 - saturate(dot(N, V));
    rim = saturate(pow(rim, _TerrainRimPower) * _TerrainRimStrength);
    rim = max(rim, 0); // Never negative
    return rim * _TerrainRimColor;
}

Seu resultado é mostrado na animação abaixo.


Ocean specular


Um dos aspectos mais originais da jogabilidade do Journey é que, às vezes, um jogador pode literalmente "surfar" nas dunas. O engenheiro- chefe John Edwards explicou que a empresa procurava tornar a areia mais sentida não sólida, mas líquida.

E isso não está totalmente errado, porque a areia pode ser percebida como uma aproximação muito grosseira de um líquido. E sob certas condições, por exemplo, em uma ampulheta, ele até se comporta como um líquido.

Para reforçar a idéia de que a areia pode ter um componente líquido, o Journey adicionou um segundo efeito de reflexão, que é freqüentemente encontrado em corpos líquidos. John Edwards chama isso de oceano especular: A idéia é obter o mesmo tipo de reflexões que são visíveis na superfície do oceano ou lago ao pôr do sol (veja abaixo).


Como antes, faremos alterações na função de iluminação LightingJourneypara adicionar um novo tipo de reflexão especular a ela.

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    // Lighting properties
    float3 L = gi.light.dir;
    float3 N = s.Normal;
    float3 V = viewDir;

    // Lighting calculation
    float3 diffuseColor	= DiffuseColor  (N, L);
    float3 rimColor     = RimLighting   (N, V);
    float3 oceanColor   = OceanSpecular (N, L, V);

    // Combining
    float3 specularColor = saturate(max(rimColor, oceanColor));
    float3 color = diffuseColor + specularColor;

    // Final color
    return float4(color * s.Albedo, 1);
}

Por que usamos no máximo dois componentes de reflexão?
, rim lighting ocean specular. , , -. , .

, .

Os reflexos espelhados na água são geralmente realizados usando a reflexão Blinn-Fong , que é uma solução de baixo custo para materiais brilhantes. Foi descrito pela primeira vez por James F. Blinn em 1977 (artigo: " Modelos de reflexão da luz para imagens sintetizadas por computador ") como uma aproximação de uma técnica anterior de sombreamento desenvolvida por Bui Tyong Fong em 1973 (artigo: " Iluminação para imagens geradas por computador ") .

Ao usar o sombreamento Blinnu-Phong, a luminosidadeI superfície I é dada pela seguinte equação:

I=(NH)powerstrength(2)


Onde

H=V+LV+L(3)


O denominador da equação (3) divide o vetor V+Lno seu comprimento. Isso garante queH tem um comprimento 1. A função de sombreador equivalente para executar esta operação é essa normalize. Do ponto de vista geométrico,H representa o vetor entre Ve Le, portanto, é chamado de meio vetor .



Por que H está entre V e L?
, , HVL.

, . VLLVVL.

, , V+LL+V, . , :


, , . , ( V+L) . , ( ).


, V+LVL, 1. , 1, ( ).

Uma descrição mais detalhada da reflexão de Blinn-Fong pode ser encontrada no tutorial Modelos de renderização e iluminação com base física . Abaixo está uma implementação simples no código shader.

float _OceanSpecularPower;
float _OceanSpecularStrength;
float3 _OceanSpecularColor;

float3 OceanSpecular (float3 N, float3 L, float3 V)
{
    // Blinn-Phong
    float3 H = normalize(V + L); // Half direction
    float NdotH = max(0, dot(N, H));
    float specular = pow(NdotH, _OceanSpecularPower) * _OceanSpecularStrength;
    return specular * _OceanSpecularColor;
}

A animação apresenta uma comparação do sombreamento difuso tradicional de acordo com Lambert e o espelho de acordo com Blinn-Fong:


Parte 5: reflexão brilhante


Nesta parte, recriaremos os reflexos brilhantes que geralmente são visíveis nas dunas de areia.

Pouco depois de publicar minha série de artigos, Julian Oberbek e Paul Nadelek fizeram sua própria tentativa de recriar uma cena inspirada no jogo Journey in Unity. O tweet abaixo mostra como eles aperfeiçoaram reflexões brilhantes para fornecer maior integridade temporal. Leia mais sobre sua implementação em um artigo sobre o IndieBurg Mip Map Folding .


Na parte anterior do curso, revelamos a implementação de duas funções de espelho na renderização de areia Journey : iluminação da borda e especular do oceano . Nesta parte, explicarei como implementar a última versão da reflexão especular: glitter .


Se você já esteve no deserto, provavelmente notou como a areia é realmente brilhante. Como discutido na parte normal da areia, cada grão de areia pode refletir a luz em uma direção aleatória. Devido à natureza dos números aleatórios, parte desses raios refletidos cairá na câmera. Por esse motivo, pontos aleatórios de areia aparecerão muito brilhantes. Esse brilho é muito sensível ao movimento, pois a menor mudança impedirá que os raios refletidos entrem na câmera.

Em outros jogos, como Astroneer e Slime Rancher, reflexões brilhantes foram usadas para areia e cavernas.



Brilho: antes e depois da aplicação do efeito.É

mais fácil avaliar essas características de brilho em uma imagem maior:



Sem dúvida, o efeito de brilho nas dunas reais depende inteiramente do fato de que alguns grãos de areia refletem aleatoriamente a luz em nossos olhos. A rigor, é exatamente isso que já modelamos na segunda parte do curso dedicado aos normais de areia, quando modelamos uma distribuição aleatória dos normais. Então, por que precisamos de outro efeito para isso?

A resposta pode não ser muito óbvia. Vamos imaginar que estamos tentando recriar o efeito de brilho apenas com a ajuda de normais. Mesmo que todos os normais sejam direcionados para a câmera, a areia ainda não brilha, porque os normais podem refletir apenas a quantidade de luz disponível na cena. Ou seja, na melhor das hipóteses, refletiremos apenas 100% da luz (se a areia for completamente branca).

Mas precisamos de outra coisa. Se quisermos que o pixel pareça tão brilhante que a luz se espalhe nos pixels adjacentes a ele, a cor deverá ser maior1. Isso aconteceu porque, no Unity, quando o filtro de bloom é aplicado à câmera usando o efeito de pós-processamento , as cores ficam mais brilhantes1espalhe para pixels vizinhos e produza uma auréola que cria a sensação de que alguns pixels estão brilhando. Essa é a base da renderização HDR .

Portanto, não, sobreposições normais não podem ser usadas de maneira simples para criar superfícies brilhantes. Portanto, esse efeito é mais conveniente para implementar como um processo separado.

Teoria de Microfaces


Para abordar a situação de maneira mais formal, precisamos perceber as dunas como consistindo em espelhos microscópicos, cada um dos quais com uma direção aleatória. Essa abordagem é chamada de teoria do microfacete , onde cada um desses pequenos espelhos é chamado de microfaceto . A base matemática dos modelos de sombreamento mais modernos é baseada na teoria das micro faces, incluindo o modelo de sombreador Standard da Unity .

O primeiro passo é dividir a superfície da duna em micro faces e determinar a orientação de cada uma delas. Como já mencionado, fizemos algo semelhante em parte do tutorial sobre as normais da areia, onde a posição UV do modelo 3D da duna foi usada para amostrar uma textura aleatória. A mesma abordagem pode ser usada aqui para anexar uma orientação aleatória a cada micro faceta. O tamanho de cada microface dependerá da escala da textura e do seu nível de texturização mip . Nossa tarefa é recriar uma certa estética, e não o desejo de fotorrealismo; essa abordagem será boa o suficiente para nós.

Após a amostragem de uma textura aleatória, podemos associar uma direção aleatória a cada grão de areia / micro faceta da duna. Vamos chamá-loG. Indica a direção do brilho , ou seja, a direção do normal dos grãos de areia que estamos observando. Um raio de luz caindo sobre um grão de areia será refletido, levando em consideração o fato de que a micro faceta é um espelho ideal orientado na direçãoG. O raio de luz refletido resultante deve entrar na câmera.R (ver abaixo).


Aqui podemos novamente usar o produto escalar Re Vpara obter medidas de seu paralelismo.

Uma abordagem é exponenciaçãoRV, conforme explicado na (quarta) parte anterior do artigo. Se você tentar fazer isso, veremos que o resultado é muito diferente do que está no Journey . Reflexões brilhantes devem ser raras e muito brilhantes. A maneira mais fácil será considerar apenas as reflexões brilhantes pelas quaisRV está abaixo de um certo valor limite.

Implementação


Podemos implementar facilmente o efeito de brilho descrito acima usando uma função reflectem Cg, o que facilita muito o cálculoR.

sampler2D_float _GlitterTex;
float _GlitterThreshold;
float3 _GlitterColor;

float3 GlitterSpecular (float2 uv, float3 N, float3 L, float3 V)
{
    // Random glitter direction
    float3 G = normalize(tex2D(_GlitterTex, uv).rgb * 2 - 1); // [0,1]->[-1,+1]

    // Light that reflects on the glitter and hits the eye
    float3 R = reflect(L, G);
    float RdotV = max(0, dot(R, V));
	
    // Only the strong ones (= small RdotV)
    if (RdotV > _GlitterThreshold)
        return 0;
	
    return (1 - RdotV) * _GlitterColor;
}

Estritamente falando, se Gcompletamente por acaso, então Rtambém será completamente aleatório. Pode parecer que o uso seja reflectopcional. E embora isso seja verdade para um quadro estático, mas o que acontece se a fonte de luz se mover? Isso pode ser devido ao movimento do próprio sol ou devido a uma fonte pontual de luz ligada ao jogador. Nos dois casos, a areia perderá a integridade temporal entre os quadros atuais e os subsequentes, devido aos quais o efeito de brilho aparecerá em locais aleatórios. No entanto, o uso da função reflectfornece renderização muito mais estável.

Os resultados são mostrados abaixo:


Como lembramos desde a primeira parte do tutorial, o componente brilho é adicionado à cor final.

#pragma surface surf Journey fullforwardshadows

float4 LightingJourney (SurfaceOutput s, fixed3 viewDir, UnityGI gi)
{
    float3 diffuseColor = DiffuseColor    ();
    float3 rimColor     = RimLighting     ();
    float3 oceanColor   = OceanSpecular   ();
    float3 glitterColor = GlitterSpecular ();

    float3 specularColor = saturate(max(rimColor, oceanColor));
    float3 color = diffuseColor + specularColor + glitterColor;
	
    return float4(color * s.Albedo, 1);
}

Há uma alta probabilidade de que alguns pixels acabem ficando mais coloridos 1, o que levará ao efeito bloom. É disso que precisamos. O efeito também é adicionado sobre a reflexão especular já existente (discutida na parte anterior do artigo), para que até grãos brilhantes de areia possam ser encontrados onde as dunas estão bem iluminadas.

Existem muitas maneiras de melhorar essa técnica. Tudo depende do resultado que você deseja alcançar. Em Astroneer e Slime Rancher , por exemplo, esse efeito é usado apenas à noite. Isso pode ser alcançado reduzindo a força do efeito de brilho, dependendo da direção da luz solar.

Por exemplo, o valor max(dot(L, fixed3(0,1,0),0))é1quando o sol cai de cima e é igual a zero quando está além do horizonte. Mas você pode criar seu próprio sistema, cuja aparência depende das suas preferências.

Por que a reflexão não é usada na reflexão de Blinn-Fong?
ocean specular, , -.

, 3D- , , . , reflect . - RVNH, .

Parte 6: ondas


Na última parte do artigo, recriaremos ondas de areia típicas resultantes da interação de dunas e vento.




Ondas na superfície das dunas: antes e depois

Teoricamente, seria lógico colocar esta parte após a parte sobre os normais da areia. Deixei no final porque é o mais difícil dos efeitos do tutorial. Parte dessa complexidade se deve à maneira como os mapas normais são armazenados e processados ​​por um sombreador de superfície que executa muitas etapas adicionais.

Mapas normais


Na parte (quinta) anterior, exploramos um método para produzir areia heterogênea. Na parte dedicada às normais de areia, uma técnica de mapeamento normal muito popular foi usada para alterar a maneira como a luz interage com a superfície da geometria . É frequentemente usado em gráficos 3D para criar a ilusão de que o objeto tem geometria mais complexa e geralmente é usado para tornar as superfícies curvas mais suaves (veja abaixo).


Para alcançar esse efeito, cada pixel é mapeado na direção do normal , indicando sua orientação. E é usado para calcular a iluminação em vez da verdadeira orientação da malha.

Ao ler as instruções dos normais a partir de uma textura aparentemente aleatória, conseguimos simular a granulação. Apesar da imprecisão física, ela ainda parece crível.

Ondas na areia


No entanto, as dunas de areia mostram outro recurso que não pode ser ignorado: as ondas. Em cada duna existem dunas menores, surgindo devido à influência do vento e mantidas juntas pelo atrito de grãos individuais de areia.

Essas ondas são muito visíveis e visíveis na maioria das dunas. Na fotografia mostrada abaixo, tirada em Omã, vê-se que a parte próxima da duna tem um padrão ondulado pronunciado.


Essas ondas variam significativamente dependendo não apenas da forma da duna, mas também da composição, direção e velocidade do vento. A maioria das dunas com pico acentuado tem ondas apenas de um lado (veja abaixo).


O efeito apresentado no tutorial foi projetado para dunas mais suaves com ondas de ambos os lados. Isso nem sempre é fisicamente preciso, mas realista o suficiente para ser crível e é um bom primeiro passo para implementações mais complexas.

Realização das ondas


Existem muitas maneiras de implementar ondas. O menos caro é simplesmente desenhá-los em uma textura, mas no tutorial queremos obter outra coisa. O motivo é simples: as ondas não são "planas" e devem interagir corretamente com a luz. Se você simplesmente desenhá-los, será impossível obter um efeito realista quando a câmera (ou o sol) se mover.

Outra maneira de adicionar ondas é alterar a geometria do modelo das dunas. Mas aumentar a complexidade do modelo não é recomendado, pois afeta muito o desempenho geral.

Como vimos na parte sobre normais de areia, você pode contornar esse problema usando mapas normais . De fato, eles são desenhados na superfície como texturas tradicionais, mas são usados ​​em cálculos de iluminação para simular geometria mais complexa.

Ou seja, a tarefa se transformou em outra: como criar esses mapas normais. A renderização manual consumirá muito tempo. Além disso, cada vez que você muda as dunas, precisará redesenhar as ondas novamente. Isso diminuirá significativamente o processo de criação de recursos, que muitos artistas técnicos procuram evitar.

Uma solução muito mais eficaz e ideal seria adicionar ondas de maneira processual . Isso significa que as direções normais das dunas mudam com base na geometria local para levar em conta não apenas os grãos de areia, mas também as ondas.

Como as ondas precisam ser simuladas em uma superfície 3D, será mais lógico implementar uma mudança na direção das normais para cada pixel. É mais fácil usar um mapa normal sem costura com um padrão de onda. Este mapa será então combinado com o mapa normal existente usado anteriormente para areia.

Mapas normais


Até esse momento, nos encontramos com três normais diferentes :

  • Geometria Normal : a orientação de cada face do modelo 3D, que é armazenada diretamente nos vértices;
  • Areia normal : calculada usando textura de ruído;
  • Onda normal : o novo efeito discutido nesta parte.

O exemplo abaixo, retirado da página de exemplos do Unity Surface Shader , demonstra uma maneira padrão de reescrever o normal de um modelo 3D. Isso requer alteração do valor o.Normal, o que geralmente é feito após a amostragem da textura (geralmente chamada de mapa normal ).

  Shader "Example/Diffuse Bump" {
    Properties {
      _MainTex ("Texture", 2D) = "white" {}
      _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert
      struct Input {
        float2 uv_MainTex;
        float2 uv_BumpMap;
      };
      sampler2D _MainTex;
      sampler2D _BumpMap;
      void surf (Input IN, inout SurfaceOutput o) {
        o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
        o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

Usamos exatamente a mesma técnica para substituir o normal da geometria pelo normal da areia.

O que é o UnpackNormal?
. ( 1), , . X, Y Z R, G B .

1+1. 01. , «» «» . (normal packing) (normal unpacking). :

R=X2+12G=Y2+12B=Z2+12(1)


:

X=2R1Y=2G1Z=2B1(2)


Unity (2), UnpackNormal. .

Normal Map Technical Details polycount.

Inclinação das dunas


No entanto, a primeira dificuldade está relacionada ao fato de a forma de onda mudar dependendo da nitidez das dunas. Dunas baixas e planas têm ondas pequenas; nas dunas íngremes, os padrões de ondas são mais distintos. Isso significa que você precisa considerar a inclinação da duna.

A maneira mais fácil de contornar esse problema é criar dois mapas normais diferentes, respectivamente, para dunas planas e íngremes. A idéia básica é misturar-se entre os dois mapas normais com base na inclinação da duna.


Mapa normal da duna íngreme


Mapa normal para uma duna plana

Mapas normais e canal azul
, .

, . , (X Y) (Z) .

length(N)=1X2+Y2+Z2=1(3)


:

X2+Y2+Z2=1X2+Y2+Z2=12Z2=1X2Y2Z=1X2Y2(4)


UnpackNormal Unity. Shader API .

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(SHADER_API_GLES)  defined(SHADER_API_MOBILE)
    return packednormal.xyz * 2 - 1;
#else
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
    return normal;
#endif
}

Unity DXT5nm, (packednormal.xy) - (packednormal.wy).

Normal Map Compression polycount.

Por que os mapas normais parecem lilás?
, , .

, . , [0,0,1].

[0,0,1][0.5,0.5,1], RGB.

; , [0,0,1].

, «». , B 1+1, 0+1. .

A inclinação pode ser calculada usando um produto escalar , que é frequentemente usado na codificação de sombreador para calcular o grau de “paralelismo” de duas direções. Nesse caso, tomamos a direção do normal da geometria (mostrada abaixo em azul) e comparamos com um vetor apontando para o céu (mostrado abaixo em amarelo). O produto escalar desses dois vetores retorna valores próximos a1quando os vetores são quase paralelos (dunas planas) e próximos a 0quando o ângulo entre eles é de 90 graus (dunas íngremes).


No entanto, estamos diante do primeiro problema - dois vetores estão envolvidos nessa operação. O vetor normal obtido da função surfusando o.Normalé expresso no espaço tangente . Isso significa que o sistema de coordenadas usado para codificar a direção normal é relativo à geometria da superfície local (veja abaixo). Nós tocamos brevemente neste tópico na parte sobre a areia normal.


Um vetor apontando para o céu é expresso no espaço do mundo . Para obter o produto escalar correto, os dois vetores devem ser expressos no mesmo sistema de coordenadas. Isso significa que precisamos transformar um deles para que ambos sejam expressos em um espaço.

Felizmente, o Unity vem em socorro com uma função WorldNormalVectorque nos permite transformar o vetor normal do espaço tangente ao espaço do mundo . Para usar esse recurso, precisamos alterar a estrutura Input, para que ela fosse incluída float3 worldNormale INTERNAL_DATA:

struct Input
{
    ...

    float3 worldNormal;
    INTERNAL_DATA
};

Isso é explicado em um artigo da documentação do Unity Writing Surface Shaders que diz:

INTERNAL_DATA — , o.Normal.

, WorldNormalVector (IN, o.Normal).

Muitas vezes, isso se torna a principal fonte de problemas ao escrever shaders de superfície. Na verdade, o valor o.Normalque está disponível na função surf, varia dependendo de como você está usando. Se você estiver lendo apenas, o.Normalcontém o vetor normal do pixel atual no espaço do mundo . Se você alterar seu valor, ele o.Normalestará no espaço tangente .

Se você gravar o.Normal, mas ainda precisar acessar o normal no espaço do mundo (como no nosso caso), poderá usá-lo WorldNormalVector (IN, o.Normal). No entanto, para isso, é necessário fazer uma pequena alteração na estrutura mostrada acima Input.

O que é INTERNAL_DATA?
INTERNAL_DATA Unity.

, , WorldNormalVector. , , . ( ).

, 3D- 3×3. , , (tangent to world matrix), Unity TtoW.

INTERNAL_DATA TtoW Input. , «Show generated code» :


, INTERNAL_DATA — , TtoW:

#define INTERNAL_DATA
    half3 internalSurfaceTtoW0;
    half3 internalSurfaceTtoW1;
    half3 internalSurfaceTtoW2;

half3x3, half3.

WorldNormalVector, , ( ) TtoW:

#define WorldNormalVector(data,normal)
    fixed3
    (
        dot(data.internalSurfaceTtoW0, normal),
        dot(data.internalSurfaceTtoW1, normal),
        dot(data.internalSurfaceTtoW2, normal)
    )

mul, TtoW , .

, :

[ToW1,1ToW1,2ToW1,3ToW2,1ToW2,2ToW2,3ToW3,1ToW3,2ToW3,3][N1N2N3]=[[ToW1,1ToW1,2ToW1,3][N1N2N3][ToW2,1ToW2,2ToW2,3][N1N2N3][ToW3,1ToW3,2ToW3,3][N1N2N3]]


LearnOpenGL.

Implementação


Um trecho do código abaixo converte o normal da tangente para o espaço do mundo e calcula a inclinação em relação à direção ascendente .

// Calculates normal in world space
float3 N_WORLD = WorldNormalVector(IN, o.Normal);
float3 UP_WORLD = float3(0, 1, 0);

// Calculates "steepness"
// => 0: steep (90 degrees surface)
//  => 1: shallow (flat surface)
float steepness = saturate(dot(N_WORLD, UP_WORLD));

Agora que calculamos a inclinação da duna, podemos usá-la para misturar os dois mapas normais. Os dois mapas normais são amostrados, planos e frios (no código abaixo eles são chamados _ShallowTexe _SteepTex). Então eles são misturados com base no valor steepness:

float2 uv = W.xz;

// [0,1]->[-1,+1]
float3 shallow = UnpackNormal(tex2D(_ShallowTex, TRANSFORM_TEX(uv, _ShallowTex)));
float3 steep   = UnpackNormal(tex2D(_SteepTex,   TRANSFORM_TEX(uv, _SteepTex  )));

// Steepness normal
float3 S = normalerp(steep, shallow, steepness);

Conforme declarado na parte sobre normais de areia, é bastante difícil combinar mapas normais corretamente, e isso não pode ser feito lerp. Nesse caso slerp, é mais correto usar , mas uma função menos cara é chamada normalerp.

Mistura de ondas


Se usarmos o código mostrado acima, os resultados podem nos decepcionar. Isso ocorre porque as dunas têm muito pouca inclinação, o que leva a uma mistura excessiva das duas texturas normais. Para corrigir isso, podemos aplicar uma transformação não linear à inclinação, o que aumentará a nitidez da mistura:

// Steepness to blending
steepness = pow(steepness, _SteepnessSharpnessPower);

Ao misturar duas texturas, é frequentemente usado para controlar sua nitidez e contraste pow. Aprendemos como e por que ele funciona no meu tutorial Renderização com base física .

Abaixo vemos dois gradientes. A parte superior mostra as cores do preto ao branco, interpoladas linearmente ao longo do eixo X com c = uv.x. Na parte inferior, o mesmo gradiente é representado por c = pow(uv.x*1.5)*3.0:



É fácil perceber, o que powpermite criar uma transição muito mais nítida entre preto e branco. Quando trabalhamos com texturas, isso reduz sua sobreposição, criando bordas mais nítidas.

Direção das dunas


Tudo o que fizemos anteriormente funciona perfeitamente. Mas precisamos resolver outro último problema. As ondas variam com a inclinação , mas não com a direção . Como mencionado acima, as ondas geralmente não são simétricas devido ao fato de o vento soprar principalmente em uma direção.

Para tornar as ondas ainda mais realistas, precisamos adicionar mais dois mapas normais (veja a tabela abaixo). Eles podem ser misturados dependendo do paralelismo da duna do eixo X ou do eixo Z.

LegalPlano
Xlegal xx plano
Zz legalz plano

Aqui precisamos implementar o cálculo do paralelismo da duna em relação ao eixo Z. Isso pode ser feito de forma semelhante ao cálculo da inclinação, mas float3 UP_WORLD = float3(0, 1, 0);pode ser usado em seu lugar float3 Z_WORLD = float3(0, 0, 1);.

Este último passo deixarei para você. Se você tiver algum problema, no final deste tutorial, há um link para baixar o pacote completo do Unity.

Conclusão


Esta é a última parte de uma série de tutoriais sobre renderização de areia do Journey.

A seguir, mostramos até onde conseguimos avançar nesta série:



Antes e depois

, quero agradecer a você por ler este tutorial bastante longo até o fim. Espero que você tenha gostado de explorar e recriar esse shader.

Agradecimentos


O videogame Journey foi desenvolvido pela Thatgamecompany e publicado pela Sony Computer Entertainment . Está disponível para PC ( Epic Store ) e PS4 ( PS Store ).

Modelos de dunas em 3D, planos de fundo e opções de iluminação foram criados por Jiadi Deng .

Um modelo 3D do personagem Journey foi encontrado no fórum do FacePunch (agora fechado).

Pacote Unity


Se você deseja recriar esse efeito, o pacote completo do Unity está disponível para download no Patreon . Tem tudo o que você precisa, desde sombreadores a modelos 3D.

Source: https://habr.com/ru/post/undefined/


All Articles