Névoa exponencial
Uma passada de pós-processamento que lê o buffer de profundidade e desbota os pixels distantes numa cor de névoa.
- 01ObjetivoAtmosfera
- 02TécnicaNévoa por distância e altura
- 03ExecuçãoNévoa exponencial
Parâmetros
Copie para a sua plataforma
- GLSLAdaptar
- GodotCopiar
Cadeia de nós
Montada passo a passo, no estilo do GLSL, com os nomes de nós dele.
Montada passo a passo, no estilo do Godot, com os nomes de nós dele.
Este card ainda não tem versão para Unreal Engine 5, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Unity, então os nós mostram os nomes do GLSL.
Este card ainda não tem versão para Blender, então os nós mostram os nomes do GLSL.
textureTexture2Dtexturetexturetexture ProfundidadeTelaUV01 Ler a cena
expExpexpexpexp Entrada02 Luz que atravessa
1.0 - xOneMinus1.0 - x1.0 - x1.0 - x Entrada03 Quantidade de névoa
mixMixmixmixmix Entrada04 Misturar com a névoa
Como funciona
Primeiro a cena é desenhada sem névoa numa textura de cor e num
buffer de profundidadeUma imagem do tamanho do framebuffer que guarda, para cada pixel, a distância da superfície mais próxima desenhada até agora. O teste de profundidade a usa para esconder o que fica atrás. Buffer de profundidade Uma imagem do tamanho do framebuffer que guarda, para cada pixel, a distância da superfície mais próxima desenhada até agora. O teste de profundidade a usa para esconder o que fica atrás.
- Ler a cena.
texture(t_screen, i.uv)busca a cor pronta do pixel, em luz linear. - Medir a distância.
sceneDepth(i.uv)devolve a distância da superfície ao plano da câmera, em unidades do mundo; o fundo recebe a distância do plano distante. Subtrair a distância inicial e limitar em zero deixa uma zona limpa em volta da câmera,travelled, o trecho de névoa que a luz de fato atravessa. - Luz que atravessa.
exp(-p_density * travelled)é a transmitânciaA fração da luz que atravessa um meio como a névoa, de 1 (toda) a 0 (nenhuma). Num meio uniforme, ela cai exponencialmente com a distância percorrida.TécnicaTransmitância
A fração da luz que atravessa um meio como a névoa, de 1 (toda) a 0 (nenhuma). Num meio uniforme, ela cai exponencialmente com a distância percorrida.
T = exp(−density · distance)Fontes: 1 na frente da câmera, caindo rumo a 0 com a distância. Cada unidade de névoa remove a mesma fração do que sobrou, então o dobro da distância eleva ao quadrado o que sobrevive em vez de reduzi-lo à metade. É por isso que a névoa se fecha de forma suave e nunca vira uma parede.- Beer–Lambert law (abre em uma nova aba)Referência
- Quantidade de névoa.
1.0 - transmittanceé a parte do pixel que a névoa substitui: preto perto da câmera, branco ao longe. - Misturar com a névoa.
mix(scene, p_fogColor, fog)mantém o que sobreviveu da superfície e preenche o resto com a cor da própria névoa, a luz que ela espalha rumo à câmera. Um cinza-azulado perto da cor do horizonte faz os objetos distantes se dissolverem no fundo.
A densidade é por unidade do mundo, então depende da escala da cena. Uma referência: a 1 / density além da distância
inicial, um pouco mais de um terço da luz da superfície ainda atravessa; no triplo disso, cerca de 5%.
A profundidade é medida ao longo do eixo de visão da câmera, não ao longo do raio de cada pixel. Perto das bordas de uma visão ampla o raio é mais longo que a profundidade, então ali a névoa fica um pouco mais fina do que a distância real daria, e ela muda levemente quando a câmera gira. Dividir a profundidade pelo cosseno do ângulo entre o raio e o eixo de visão dá a distância real; isso exige o campo de visão da câmera, então este card deixa isso de fora.
Monte no GLSL
O shader é uma única passada de tela cheia, mas lê duas texturas que o seu renderizador precisa criar antes. Renderize a cena num framebuffer com uma textura de cor (t_screen) e uma textura que guarde a profundidade de visão de cada pixel no canal w (u_sceneNormalDepth), e então desenhe um triângulo de tela cheia com este shader no canvas.
Primeiro a cena renderiza num framebuffer fora da tela com dois anexos de cor: a cor, e um segundo em que o shader de
fragmento da cena escreve a profundidade de visão, -(view * vec4(world, 1.0)).z, em w. As duas são texturas
RGBA16F. Depois:
- Vincule o framebuffer padrão e desenhe um triângulo que cubra a tela, passando um
v_uvde 0 a 1. - Vincule a textura de cor a
t_screene a de profundidade au_sceneNormalDepth, as duas com filtragem linear e clamp to edge. - Envie
p_density,p_startep_fogColor(decodificada de sRGB para linear) e desenhe.
Se o seu renderizador já guarda uma textura de profundidade, remova u_sceneNormalDepth e reescreva sceneDepth()
para amostrá-la e converter a profundidade de janela em distância de visão. O resto do shader fica como está.
Monte no Godot
Roda como está no renderizador Forward+. No Mobile, apague a linha do uniform u_normalRoughness; este card nunca o lê, e o Mobile recusa esse hint. O renderizador Compatibility guarda a profundidade de -1 a 1, então nele sceneDepth() também precisa montar a coordenada de profundidade como depth * 2.0 - 1.0 antes da projeção inversa.
- Adicione um MeshInstance3D como filho da sua Camera3D, dê a ele um QuadMesh com Size 2 × 2 e ponha a Position em (0, 0, -1), para ele ficar na frente do plano próximo.
- Dê a ele um novo ShaderMaterial e cole o shader. O
vertex()dele escreve os cantos do quad direto nos cantos da tela; a posição dele só serve para ele não ser descartado. - Ajuste density, start e fogColor no material. O shader lê a textura de profundidade do Godot e a transforma numa distância com a matriz de projeção inversa, então start e density ficam nos metros da sua cena.
No VisualShader, o mesmo grafo começa de um nó LinearSceneDepth, que dá a mesma distância ao plano da câmera, e de um nó Texture2D com a fonte em Screen. Encadeie-os por Subtract, Max, Exp, OneMinus e Mix como nos passos acima e escreva o resultado em Albedo num material unshaded.
Ainda não há tutorial de Unreal Engine 5 para este card.
Ainda não há tutorial de Unity para este card.
Ainda não há tutorial de Blender para este card.
Código
#version 300 es// Distance & height fog → Exponential fog, web GLSL (GLSL ES 3.00). From Creative Center, MIT license.precision highp float;uniform float u_time;uniform vec2 u_resolution; // this pass's output size in pixelsuniform float p_density; // Density: 0.12 (0.0 to 0.5)uniform float p_start; // Start distance: 2.0 (0.0 to 20.0)uniform vec3 p_fogColor; // Fog color: vec3(0.78, 0.81, 0.86), sRGB; upload it decoded to linearuniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgeuniform sampler2D u_sceneNormalDepth; // the scene's world normal in xyz (0 where nothing was drawn) and view depth in win vec2 v_uv;in vec3 v_position;in vec3 v_normal;in vec3 v_view;out vec4 fragColor;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// The scene's depth at uv: distance from the camera plane in world units; the far plane's where nothing was drawnfloat sceneDepth(vec2 uv) { return texture(u_sceneNormalDepth, uv).w;}vec3 u_toSrgb(vec3 c) { return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));}// sceneDepth() is the distance from the camera plane, not along the pixel's ray, so the fog doesn't thicken toward// the corners of the screen the way true distance would; the card explains when that matters.vec4 effect(Inputs i) { vec3 scene = texture(t_screen, i.uv).rgb; float depth = sceneDepth(i.uv); float travelled = max(depth - p_start, 0.0); // Beer–Lambert: each unit of fog lets the same fraction of the light through, so what survives falls exponentially float transmittance = exp(-p_density * travelled); float fog = 1.0 - transmittance; vec3 color = mix(scene, p_fogColor, fog); return vec4(color, 1.0);}void main() { Inputs i; i.uv = v_uv; i.position = v_position; // Interpolation shortens the vectors between vertices i.normal = normalize(v_normal); i.view = normalize(v_view); i.time = u_time; i.fragCoord = gl_FragCoord.xy; i.resolution = u_resolution; vec4 frag_out = effect(i); // The output is sRGB: clamped and encoded once, here, with no tone mapping; opaque, so alpha is 1 fragColor = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}// Distance & height fog → Exponential fog, Godot 4. From Creative Center, MIT license.shader_type spatial;// A full-screen quad over the finished frame: no lighting, no culling, never hidden by depthrender_mode unshaded, cull_disabled, depth_test_disabled;uniform float density : hint_range(0.0, 0.5, 0.005) = 0.12;uniform float start : hint_range(0.0, 20.0, 0.1) = 2.0;uniform vec3 fogColor : source_color = vec3(0.78, 0.81, 0.86);uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;uniform sampler2D u_depth : hint_depth_texture, filter_nearest, repeat_disable;// Normals come from Forward+ only (the Mobile and Compatibility renderers don't keep them)uniform sampler2D u_normalRoughness : hint_normal_roughness_texture, filter_nearest, repeat_disable;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// vertex() hands its matrices to sceneDepth() and sceneNormal()varying flat mat4 v_invProjection;// The scene's depth at uv: distance from the camera plane in world units; the far plane's where nothing was drawnfloat sceneDepth(vec2 uv) { // The effect's uv starts at the bottom-left; the screen's at the top-left vec2 screen = vec2(uv.x, 1.0 - uv.y); // Forward+ stores reversed-Z depth: undo the projection to get the view-space point vec4 view = v_invProjection * vec4(screen * 2.0 - 1.0, texture(u_depth, screen).r, 1.0); return -view.z / view.w;}// Godot's textures start at the top-left and the effect's uv at the bottom-left: each sample flips v backvec2 u_flipUv(vec2 uv) { return vec2(uv.x, 1.0 - uv.y);}// sceneDepth() is the distance from the camera plane, not along the pixel's ray, so the fog doesn't thicken toward// the corners of the screen the way true distance would; the card explains when that matters.vec4 effect(Inputs i) { vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb; float depth = sceneDepth(i.uv); float travelled = max(depth - start, 0.0); // Beer–Lambert: each unit of fog lets the same fraction of the light through, so what survives falls exponentially float transmittance = exp(-density * travelled); float fog = 1.0 - transmittance; vec3 color = mix(scene, fogColor, fog); return vec4(color, 1.0);}void vertex() { // Put the shader on a MeshInstance3D with a 2x2 QuadMesh in front of the camera; it covers the screen wherever it is POSITION = vec4(VERTEX.xy, 1.0, 1.0); v_invProjection = INV_PROJECTION_MATRIX;}void fragment() { Inputs i; // SCREEN_UV starts at the top-left; the effect expects GLSL's bottom-left i.uv = vec2(SCREEN_UV.x, 1.0 - SCREEN_UV.y); // A flat quad facing the viewer, the same as the site's 2D preview i.position = vec3(i.uv * 2.0 - 1.0, 0.0); i.normal = vec3(0.0, 0.0, 1.0); i.view = vec3(0.0, 0.0, 1.0); i.time = TIME; // FRAGCOORD's origin is the top-left too i.resolution = VIEWPORT_SIZE; i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y); vec4 frag_out = effect(i); ALBEDO = frag_out.rgb;}Ainda não há código de Unreal Engine 5 para este card.
Ainda não há código de Unity para este card.
Ainda não há código de Blender para este card.
Leitura complementar
- Better fog (abre em uma nova aba)Artigo
- Beer–Lambert law (abre em uma nova aba)Referência
- glFog (abre em uma nova aba)Referência
- exp (abre em uma nova aba)Referência
- mix (abre em uma nova aba)Referência
- Depth testing (abre em uma nova aba)Artigo