Filme antigo
Uma cópia em sépia com cantos escuros, exposição que cintila e um grão que muda 24 vezes por segundo.
- 01ObjetivoLente
- 02TécnicaVinheta e granulação
- 03ExecuçãoFilme antigo
Parâmetros
Copie para a sua plataforma
- GLSLCopiar
- 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.
dotDotProductdotdotdot UVTempoTela01 Brilho
*Multiply*** Entrada02 Tingir
smoothstepSmoothStepsmoothstepsmoothstepsmoothstep Entrada03 Vinheta
hashRandomRangehashhashhash Entrada04 Granulação
*Multiply*** Entrada05 Combinar
Como funciona
O card transforma a cena de teste numa projeção gasta: um único brilho tingido em vez de cor, cantos que caem para o
escuro, uma cintilação no quadro inteiro e um grão fino. Tudo acontece numa única passada de
pós-processamentoPassadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui. Cadeia de pós-processamento Passadas de tela cheia rodadas em ordem sobre o quadro pronto, cada uma lendo a imagem que a anterior escreveu. Bloom, tone mapping e correção de cor ficam aqui.
Passo a passo
- Brilho.
lumaé a luminânciaO quanto uma cor parece clara, a partir do vermelho, verde e azul lineares, pesados pela sensibilidade do olho a cada um.FundamentoLuminância
O quanto uma cor parece clara, a partir do vermelho, verde e azul lineares, pesados pela sensibilidade do olho a cada um.
Y = 0.2126 R + 0.7152 G + 0.0722 BFontesda cena, o produto escalar da cor com o quanto cada canal parece claro ao olho. O filme antigo registra luz, não matiz, então esse número é tudo o que a cópia guarda. - Tingir.
toned = luma * p_tintmancha esse brilho com a cor do filme, um sépia quente por padrão. Como op_tintchega em luz linear, o tom escala a luz do jeito que um filtro colorido faria. - Vinheta.
centeredmede o pixel a partir do meio do quadro em alturas de tela, para o escurecimento continuar redondo num quadro largo em vez de seguir o retângulo.falloffvale 1 dentro dep_radiuse cai para 0 ao longo dep_softness, por um smoothstepUm limiar suave. Vale 0 abaixo da primeira borda, 1 acima da segunda e segue uma curva em S com as pontas planas entre elas.FundamentoSmoothstep
Um limiar suave. Vale 0 abaixo da primeira borda, 1 acima da segunda e segue uma curva em S com as pontas planas entre elas.
t = clamp((x − e0) / (e1 − e0), 0, 1); s = t² (3 − 2t)Fontesque não deixa borda visível.- smoothstep (abre em uma nova aba)Referência
- Smoothstep (abre em uma nova aba)Referência
- Granulação.
speckleé um hashUma função que embaralha a entrada num valor que parece aleatório, mas é sempre o mesmo para a mesma entrada. Shaders a usam para ter aleatoriedade por pixel e por célula.FundamentoHash
Uma função que embaralha a entrada num valor que parece aleatório, mas é sempre o mesmo para a mesma entrada. Shaders a usam para ter aleatoriedade por pixel e por célula.
h = fract(sin(dot(p, k)) × big)da célula do pixel e do quadro do filme: um valor em 0..1 que parece aleatório, diferente para cada célula e cada quadro. As células têmp_grainSizepixels de largura, então o grão pode ser mais grosso que a tela. O número do quadro étime × 24, arredondado para baixo: grão e cintilação mudam só 24 vezes por segundo, como num projetor, o que parece filme, enquanto um ruído novo a cada quadro parece chuvisco de vídeo. - Combinar.
filmedmultiplica tudo: o brilho tingido, a vinheta, a cintilação (um hash por quadro, igual para todos os pixels) e o grão, centrado em 1 para clarear e escurecer por igual.
Por que o grão multiplica
Ruído somado levanta o preto para cinza e parece uma lente suja. Ruído multiplicado acompanha a imagem: o preto
continua preto, as áreas claras ganham o salpicado mais forte, e o grão segue a imagem em vez de flutuar por cima dela.
O grão de filme de verdade aparece mais nos meios-tons; uma versão de produção pode moldar o p_grain pelo luma para
chegar lá.
O hash usa inteiros, não o conhecido truque fract(sin(...)). Esse truque depende de como a GPU arredonda um seno
grande, o que muda entre fabricantes e cai em padrões visíveis com entradas altas; um hash de inteiros dá os mesmos bits
em qualquer GPU. A função de mistura é o finalizador do MurmurHash3, que espalha cada bit da entrada pela saída.
Notas de produção
Uma leitura de textura e cerca de 95 operações por pixel, a maioria nos dois hashes (três rodadas de deslocamentos, XORs e multiplicações cada). Sai muito mais barato que qualquer desfoque, e barato o bastante para ficar ligado no jogo inteiro. O hash da cintilação é o mesmo para todos os pixels de um quadro, então uma versão de produção o calcula uma vez na CPU e o passa como uniform.
Rode-o por último, depois do tone mapping e da correção de cor, como as engines fazem. A cena da prévia entra direto, então as luzes mais fortes estouram, primeiro na cor do tom e depois no branco, quando até o azul dele passa de 1, o que combina com uma cópia antiga e queimada; num jogo, aplique o tone mapping antes, para o grão não ser comprimido junto com os brilhos.
O grão também esconde o bandingBanding são as faixas em que um degradê suave se parte quando guardado com poucos níveis. O dithering soma um ruído minúsculo por pixel antes do arredondamento, e as faixas viram uma granulação fina. Banding e dithering Banding são as faixas em que um degradê suave se parte quando guardado com poucos níveis. O dithering soma um ruído minúsculo por pixel antes do arredondamento, e as faixas viram uma granulação fina.
Monte no GLSL
Uma passada de pós. Renderize a cena numa textura e depois desenhe no canvas um triângulo de tela cheia com este shader de fragmento, a cena ligada em t_screen, u_resolution com o tamanho do canvas e u_time em segundos.
O código copiado é um shader de fragmento completo. Ele roda sobre um triângulo de tela cheia cujo shader de vértice
escreve v_uv de 0 a 1 de ponta a ponta, e escreve sRGB, já limitado, pronto para o canvas.
- Desenhe a cena num framebuffer com uma textura de cor, em luz linear. Uma textura
RGBA16Fguarda as luzes acima de 1 (o WebGL2 precisa deEXT_color_buffer_floatpara desenhar nela);RGBA8também serve, já que este card as corta de qualquer jeito. - Ligue o canvas e desenhe o triângulo de tela cheia com este shader e a textura da cena ligada em
t_screen. - A cada quadro, defina
u_timecom os segundos desde o início eu_resolutioncom o tamanho do canvas em pixels. - Envie o
p_tintdecodificado para linear (o comentário ao lado dele avisa) e defina os outros uniformsp_com os valores do card.
Para usar só a vinheta, ponha p_grain e p_flicker em 0 e um p_tint branco; a cor continua virando cinza, então,
para uma vinheta colorida, troque toned por scene.
Monte no Godot
Um shader spatial para um quad de tela cheia na frente da câmera, que lê o quadro por hint_screen_texture. Ele roda antes do tone mapping do Godot, então ponha o tone mapper do Environment em Linear para ter o visual exato do card.
O shader copiado está pronto para usar; o Godot desenha uma passada de pós como uma malha na frente da câmera, e a função de vértice deste shader a estica sobre a tela inteira.
- Adicione um
MeshInstance3Dcomo filho da suaCamera3De dê a ele umQuadMeshde tamanho 2×2. - Dê a ele um novo
ShaderMateriale cole o shader num novoShader. - Aumente o Extra Cull Margin da malha até o máximo (em Geometry), para o Godot nunca descartá-la como fora da tela.
- No
WorldEnvironment, ponha Tonemap > Mode em Linear. - Ajuste
tint,radius,softness,grain,grainSizeeflickernos Shader Parameters do material.
O shader lê o TIME, que continua correndo no editor, então o grão e a cintilação também se animam no viewport.
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// Vignette & film grain → Old film, 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 vec3 p_tint; // Tint: vec3(1.0, 0.86, 0.66), sRGB; upload it decoded to linearuniform float p_radius; // Vignette radius: 0.35 (0.0 to 1.0)uniform float p_softness; // Vignette softness: 0.6 (0.05 to 1.5)uniform float p_grain; // Grain: 0.35 (0.0 to 1.0)uniform float p_grainSize; // Grain size (px): 1.5 (1.0 to 4.0)uniform float p_flicker; // Flicker: 0.12 (0.0 to 0.5)uniform sampler2D t_screen; // the scene's color: linear HDR (RGBA16F), linear filter, clamp to edgein 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;};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));}// MurmurHash3's finalizer (public domain): every input bit ends up flipping about half of the output bits. highp, because// a fragment shader's integers default to mediump, which some mobile GPUs keep in 16 bitshighp uint mix32(highp uint x) { x ^= x >> 16u; x *= 0x85ebca6bu; x ^= x >> 13u; x *= 0xc2b2ae35u; x ^= x >> 16u; return x;}// A value in 0..1 that looks random, one per cell and film frame; an integer hash gives the same bits on every GPUfloat hash(highp ivec2 cell, highp int frame) { highp uint h = mix32(uint(cell.x) ^ mix32(uint(cell.y) ^ mix32(uint(frame)))); return float(h >> 8u) / 16777216.0;}vec4 effect(Inputs i) { vec3 scene = texture(t_screen, i.uv).rgb; float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722)); vec3 toned = luma * p_tint; // Measured in screen heights from the center, so the darkening stays round on a wide frame vec2 centered = (i.uv - 0.5) * vec2(i.resolution.x / i.resolution.y, 1.0); float falloff = 1.0 - smoothstep(p_radius, p_radius + p_softness, length(centered)); // A projector shows 24 frames a second: grain and flicker change only that often, so they read as film, not static highp int frame = int(i.time * 24.0); float speckle = hash(ivec2(i.fragCoord / p_grainSize), frame); float flash = 1.0 + (hash(ivec2(0), frame) - 0.5) * p_flicker; // Grain scales the color rather than adding to it, so black stays black and the grain follows the picture vec3 filmed = toned * falloff * flash * (1.0 + (speckle - 0.5) * p_grain); return vec4(filmed, 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);}// Vignette & film grain → Old film, 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 vec3 tint : source_color = vec3(1.0, 0.86, 0.66);uniform float radius : hint_range(0.0, 1.0, 0.01) = 0.35;uniform float softness : hint_range(0.05, 1.5, 0.01) = 0.6;uniform float grain : hint_range(0.0, 1.0, 0.01) = 0.35;uniform float grainSize : hint_range(1.0, 4.0, 0.1) = 1.5;uniform float flicker : hint_range(0.0, 0.5, 0.01) = 0.12;uniform sampler2D t_screen : hint_screen_texture, filter_linear, repeat_disable;struct Inputs { vec2 uv; vec3 position; vec3 normal; vec3 view; float time; vec2 fragCoord; vec2 resolution;};// 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);}// MurmurHash3's finalizer (public domain): every input bit ends up flipping about half of the output bits. highp, because// a fragment shader's integers default to mediump, which some mobile GPUs keep in 16 bitshighp uint mix32(highp uint x) { x ^= x >> 16u; x *= 0x85ebca6bu; x ^= x >> 13u; x *= 0xc2b2ae35u; x ^= x >> 16u; return x;}// A value in 0..1 that looks random, one per cell and film frame; an integer hash gives the same bits on every GPUfloat hash(highp ivec2 cell, highp int frame) { highp uint h = mix32(uint(cell.x) ^ mix32(uint(cell.y) ^ mix32(uint(frame)))); return float(h >> 8u) / 16777216.0;}vec4 effect(Inputs i) { vec3 scene = texture(t_screen, u_flipUv(i.uv)).rgb; float luma = dot(scene, vec3(0.2126, 0.7152, 0.0722)); vec3 toned = luma * tint; // Measured in screen heights from the center, so the darkening stays round on a wide frame vec2 centered = (i.uv - 0.5) * vec2(i.resolution.x / i.resolution.y, 1.0); float falloff = 1.0 - smoothstep(radius, radius + softness, length(centered)); // A projector shows 24 frames a second: grain and flicker change only that often, so they read as film, not static highp int frame = int(i.time * 24.0); float speckle = hash(ivec2(i.fragCoord / grainSize), frame); float flash = 1.0 + (hash(ivec2(0), frame) - 0.5) * flicker; // Grain scales the color rather than adding to it, so black stays black and the grain follows the picture vec3 filmed = toned * falloff * flash * (1.0 + (speckle - 0.5) * grain); return vec4(filmed, 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);}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
- Vignetting (abre em uma nova aba)Referência
- Film grain (abre em uma nova aba)Referência
- MurmurHash3 (abre em uma nova aba)Referência
- Relative luminance (abre em uma nova aba)Referência
- smoothstep (abre em uma nova aba)Referência
- length (abre em uma nova aba)Referência