Como posso fazer a água parecer mais escura com a profundidade, como no Minecraft?


24

No Minecraft, quando você olha para a água, quanto mais profunda você vê, mais escura fica. Alguém sabe como codificar algo assim?

Minecraft com efeito minecraft com efeito

jogo semelhante sem efeito jogo semelhante sem efeito


18
Isso não é feito automagicamente, já que o material do cubo de água é semi-transparente?
pek

Acho que não. Eu adicionei uma imagem sem o efeito para comparação.
Xavier

2
Talvez seja um efeito de mistura aditiva aplicado apenas nos cubos de água? Novamente, isso deve ser fácil, pois o material é semi-transparente.
pek

11
você também pode alterar a cor das caixas de acordo com a profundidade.
precisa saber é o seguinte

Respostas:


51

Existem essencialmente duas abordagens diferentes para iluminar a água com base na profundidade:

Voxel-Lighting

O Minecraft usa iluminação baseada em voxel, que funciona propagando a luz para cubos adjacentes, diminuindo o brilho dependendo do tipo de bloco. Oceanos escuros são um efeito colateral desse sistema.

A água bloqueia a luz solar e reduz a luz em 3 níveis por bloco (em vez do nível 1 padrão), o que significa que o brilho no oceano para cada distância da superfície é:

0 (surface): 15 (direct sunlight)
1:           12
2:            9
3:            6
4:            3
5 and below:  0 (darkness)

Fonte: Minecraft Wiki - Light

Sombreamento com base na distância

Em jogos com um modelo de iluminação tradicional, esse efeito pode ser criado medindo a quantidade de água que fica entre a fonte de luz e o fundo do oceano. A luz é então apagada com base nessa distância. Existem alguns métodos para fazer isso:

Cálculo Direto

Se você tiver uma superfície plana, poderá calcular facilmente a distância que a luz percorre na água se afastar a superfície normal da massa de água \ vec {n}e do produto escalar dessa normal e uma posição da superfície sno sombreador de geometria.

A distância efetiva da água é

\ max (\ left (s - \ vec {n} \ cdot \ vec {p} \ right), 0) \ cdot \ left (1 + \ tan (\ alpha) \ right)

onde \ vec {p}é a posição do vértice e alfaé o ângulo entre a direção da luz abaixo da superfície e a superfície da água normal em relação ao corpo de água.

Ao pôr do sol, alfaatinge apenas um pouco menos de 50 ° porque a luz é refratada ao entrar na água.
Aqui está uma postagem no blog com uma boa explicação: A câmera digital: reflexão interna total
Outro post com mais detalhes: A câmera digital: lei da refração de Snell

Se você estiver usando um mapa de altura em uma superfície paralela à água, \ left (s - \ vec {n} \ cdot \ vec {p} \ right)torna-se \ esquerda (s - h \ direita). O fator certo é igual a 1 se o sol estiver diretamente acima da superfície da água.
Com uma luz pontual, é necessário calcular alfapara cada vértice com base na posição relativa da fonte de luz.

Com um nível de água fixo ou uma direção de luz fixa, partes da equação são constantes e não devem ser calculadas no sombreador por razões de desempenho.

Prós:

  • Rápido e preciso

Contras:

  • Funciona apenas para superfícies planas de água ou apenas para a luz diretamente acima, pois normalmente apenas uma superfície é levada em consideração. (A combinação de uma superfície rugosa e luz inclinada pode funcionar até certo ponto com o mapeamento de paralaxe.)
  • Sem cáusticos

Mapeamento de Sombra

Se você renderizar a superfície da água em um mapa de profundidade separado (como visto da fonte de luz), poderá usar essa textura de profundidade para calcular a distância que a luz percorre na água antes de atingir a superfície.
Para fazer isso, você projeta cada vértice na projeção de exibição da fonte de luz no shader de vértice e faz a pesquisa de textura no shader de pixel.

Se a superfície for relativamente plana, use uma origem de luz refratada para obter melhores resultados.

Prós:

  • Funciona com geometria da água relativamente complexa, desde que não se oclua. *
  • Pode ser reutilizado para quase qualquer tipo de volume parcialmente transparente.

Contras:

  • Mais lento que o cálculo direto.
  • Precisa de VRAM adicional para o mapa de profundidade.
  • Não é 100% preciso.

* Você pode determinar a quantidade de água na frente da superfície sólida mais próxima contando a profundidade do ponto de vista da luz da seguinte maneira:

  1. Renderize a geometria sólida em sua cena como normal. Para cada fragmento, você adiciona o valor da profundidade à textura do resultado.
  2. Renderize as faces frontais da água sem atualizar o buffer de profundidade e subtraia as profundidades dos fragmentos do resultado.
  3. Renderize as faces posteriores da mesma maneira, mas adicione a profundidade do fragmento ao resultado.

A textura do resultado agora contém a quantidade de água na frente da luz no espaço de visualização da luz, portanto, o valor deve ser transformado novamente antes de você usá-lo. Este método funciona para calcular a luz direcional (menos refração), mas levará a luz ambiente incorreta se as superfícies forem muito irregulares e houver uma grande quantidade de ar entre os corpos de água afetando os mesmos fragmentos.
Os prós e os contras são os mesmos do mapeamento de sombra normal, exceto que você precisa de mais um buffer ao calcular a profundidade e o desempenho é pior porque é necessário desenhar mais geometria.

Rastreamento de raio

O traçado de raios é de longe a solução mais precisa, mas também a mais cara, para renderizar volumes transparentes. Há duas maneiras de fazer isso: 1. Rastreamento do fundo do oceano em direção à superfície e 2. Rastreamento da fonte de luz em direção à água. São necessários vários raios para cada ponto no chão para calcular o brilho.

Prós:

  • Funciona corretamente com todas as geometrias.
  • Cáusticos corretos!

Contras:

  • Lento!

Efeitos adicionais

Há mais algumas coisas a serem levadas em consideração ao renderizar água:

Névoa

A luz na água é espalhada novamente enquanto viaja para o observador, portanto você deve misturá-la para obter uma cor sólida.

Se o observador estiver submerso , você poderá renderizar a névoa com base no resultado final do buffer de profundidade. A cor do nevoeiro, mas não sua densidade, deve mudar com a distância do observador da superfície! (O Minecraft usa apenas essa parte do efeito.)

Se o observador olhar para a água de cima , é necessário calcular a neblina com base na diferença de profundidade entre a superfície e a geometria sob a água. A cor do nevoeiro deve ficar um pouco mais escura com diferenças de profundidade maiores, mas deve mudar apenas para o ponto em que o nevoeiro é totalmente opaco.

A cor do nevoeiro também deve depender da direção da visualização de cada pixel, por isso é um pouco mais escura ao olhar para baixo nos dois casos.

Caustics falsificados

Se você usar uma Textura 3D lado a lado em vez de um decalque para cáusticos falsos, poderá evitar esticar em superfícies verticais. A força da luz dispersa perto da superfície varia em três dimensões; portanto, o uso de uma textura 2D geralmente produz alongamentos em algum lugar da cena. Você pode modelar os ângulos de mudança de luz projetando as posições dos vértices do piso em um sistema de coordenadas diferente.

Outra possibilidade é calcular a densidade da luz com base na posição da superfície no sistema de coordenadas da luz, embora isso provavelmente custe algum desempenho.

Os cáusticos devem desaparecer mais rapidamente que a luz difusa com o aumento da profundidade.

Gradiente de cor

As cores são espalhadas de maneira diferente, portanto a cor da luz deve mudar com o aumento da profundidade. Isso também evita arestas abruptas onde, por exemplo, uma praia cruza a superfície da água.

Ângulo de incidência

Por causa da refração, a luz atinge o fundo do oceano muito mais íngreme do que normalmente. O artigo da Wikipedia sobre a lei de Snell possui fórmulas para ângulos e vetores.


6

Acredito que o efeito de iluminação do céu no Minecraft seja direto - as coisas ficam sombreadas pelo que está acima deles, não importa onde esteja o sol. Em seguida, a iluminação local de tochas etc. é aplicada com um efeito de queda - quanto mais longe a fonte de luz, menos luz um cubo recebe.

Se feito dessa maneira, cada camada de água sombrearia cumulativamente a camada abaixo dela, tornando-se progressivamente mais escura. A folhagem das árvores fornece sombra assim, porém não é cumulativa. Você fica com a mesma sombra debaixo de uma árvore, seja 1 ou 100 cubos de folhagem.

Uma pista de que este é o método usado é que a água não fica mais escura quando está mais longe do espectador - apenas quando você desce. Sim, o efeito de neblina entra à distância, mas não o efeito escuro da água.

Portanto, a fórmula básica para calcular a iluminação seria algo parecido com isto no pseudo-código ...

light_on_cube = 1.0
for each cube above target cube, from lowest to highest {
   if cube being examined is tree foliage
      light_on_cube = 0.5
   else if cube being examined is water
      light_on_cube = light_on_cube - 0.1
   else if cube being examined is solid 
      light_on_cube = 0
}

Isso não é perfeito para calcular a iluminação sob saliências ou cavernas, pois seria muito escuro sob uma saliência usando esse método. Mas pode-se adicionar fontes de luz locais (tochas, incêndios etc.), além de tratar os blocos iluminados pelo sol como fontes de luz. Algo assim pode fazer isso ...

  1. Calcule a luz do sol diretamente acima (via pseudo-código acima) para cada cubo.
  2. Se um cubo tiver uma fonte de luz ao lado, considere-a totalmente iluminada (1.0)
  3. Se um cubo não receber sol diretamente acima, forneça alguma luz com base na distância que ele está de um cubo totalmente iluminado. Mais perto significa mais luz, mais longe significa menos (até zero).

A idéia aqui é que, se um cubo for aceso pelo sol ou uma tocha, o cubo ao lado dele também será aceso de alguma maneira. E quanto mais longe você estiver daquele cubo aceso, menos luz haverá. É uma maneira de estimar a iluminação difusa, mas acho que (?) Funcionaria.


11
Sim, tenho certeza de que esse é o bilhete. Eu fiz algo semelhante no meu jogo.
MichaelHouse

Aliás, acabei de adicionar seu blog à minha lista de leitores do google Byte56 - blogs de desenvolvedor FTW!
Tim Holt

Oh, porque obrigado. Ainda estou fora do tópico desta pergunta, mas acabei de ler seu blog sobre a aula do professor Bailey. Eu estava nessa aula no ano passado! Tenho certeza de que você fez essa apresentação no ano passado também. Eu pensei que seu nome era familiar. Mundo pequeno :)
MichaelHouse

3

Talvez eu esteja entendendo mal a pergunta, mas por que você não pode simplesmente mudar a cor dos blocos, dependendo da profundidade deles?

Se você tiver a profundidade d (em blocos, começando em 0), uma equação razoável para o brilho seria:

L = (1- m ) e - kd + m

Código: L = (1.0 - m) * exp(-k * d) + m;

k controla o quão rápido fica mais escuro (mais alto = mais rápido). Um valor razoável seria 0,5.
m é o brilho mínimo que você deseja.
L varia de 0 a 1.

Se você não souber como alterar a cor dos blocos em qualquer API gráfica que estiver usando, faça isso como uma pergunta separada (informando qual API você usa e se está usando shaders).


Eu simplesmente não pensei em fazer isso. Só por curiosidade, onde você conseguiu essa equação?
Xavier

11
@ Xavier: Acabei de inventar. O e^-kdbit é apenas uma deterioração exponencial, que é uma função padrão para coisas que tendem gradualmente a zero em algum valor (profundidade). A multiplicação (1-m)e a adição de msão apenas para escalar e compensar o decaimento, para que ele termine no mínimo, mmas ainda comece às 1. en.wikipedia.org/wiki/Exponential_decay
Peter Alexander

O problema é que os blocos de uma tonalidade mais profunda só serão vistos se os blocos tiverem cores alfa; Nesse caso, não há necessidade de alterar a cor do bloco, o alfa criará o efeito automaticamente.
Jonathan Connell

@ Jonathan: Você não processa os blocos de água, processa os blocos no fundo do mar com a cor escurecendo e depois tem apenas uma camada alfa na superfície da água.
Peter Alexander

@ Peter Alexander Ok, presumi que, nesses jogos do tipo bloco, até a água era feita de blocos.
Jonathan Connell
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.