Pular para o conteúdo
Forma · Círculos, caixas e linhasIniciante

Anel pulsante

Um anel fino cujo raio cresce e encolhe como uma respiração, com um seno do tempo.

UVTempoSuperfície
Abrir no Playground
  1. 01ObjetivoForma
  2. 02TécnicaCírculos, caixas e linhas
  3. 03ExecuçãoAnel pulsante
Quad de tela cheia

Parâmetros

0.50
0.040
0.10
3.0
#59d9ff
#0d0f1a

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.

  1. lengthLength2Dlengthlengthlength
    UV
    Tempo

    01 Distância até o centro

  2. sinSinsinsinsin

    02 Raio ao longo do tempo

  3. absAbsabsabsabs

    03 Distância até a linha

  4. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    04 Recortar a linha

  5. mixMixmixmixmix

    05 Colorir

Como funciona

O anel é o Ponto suave com duas mudanças: o limiar mede a distância até uma linha em vez de até uma área, e essa linha se move com o tempo.

Distância até o centro. Como no Ponto suave, a UV é centralizada, o x é multiplicado pela proporção da tela para os círculos continuarem redondos, e length(p) dá a distância de cada pixel até o meio.

Um raio que respira. sin transforma um número que cresce sem parar numa oscilação suave entre -1 e 1 e de volta, uma vez a cada 2π. Passar i.time * speed para ele faz a oscilação se repetir com o tempo, mais rápido com uma velocidade maior. Multiplicar pela amplitude do pulso escala a oscilação, e somar o raio base a centraliza ali: o raio agora vai de radius - pulse a radius + pulse e desacelera nas duas pontas, e é isso que faz o movimento parecer uma respiração, e não um quique.

Distância até a linha. d - radius é negativo dentro do círculo e positivo fora, então um limiar sobre ele faz um disco. abs() dobra o lado de dentro sobre o de fora: o resultado é a distância do pixel até a linha do círculo, de qualquer um dos lados. Agora os pixels sobre a linha valem 0 e as duas direções crescem a partir dela.

Recorte a linha. Um smoothstep em torno da espessura da linha guarda todo pixel mais perto da linha do que essa espessura. A rampa tem dois pixels de largura (2.0 / altura é um pixel nessas unidades), então as bordas ficam sem serrilhado dos dois lados do anel e a linha mantém a mesma espessura em qualquer raio.

Pinte. mix põe a cor do anel onde a cobertura vale 1 e o fundo onde vale 0.

A espessura continua constante enquanto o anel cresce, porque ela é medida no shader, depois do movimento. Escalar um sprite de anel engrossaria a linha junto. Para um brilho, misture um segundo smoothstep, mais largo e mais fraco; para o anel sumir conforme cresce, multiplique a cobertura por 1.0 - (pulsed - p_radius + p_pulse) / (2.0 * p_pulse).

Monte no GLSL

O código copiado é um shader de fragmento WebGL2 completo, desenhado num retângulo que cobre o canvas.

  1. Desenhe dois triângulos cobrindo o canvas no espaço de recorte, com um shader de vértice que escreva v_uv de 0 a 1 e constantes em v_position, v_normal e v_view (o shader de fragmento declara os quatro).
  2. A cada quadro, defina u_time com os segundos desde o início (performance.now() / 1000 menos o instante inicial) e u_resolution com o tamanho do drawing buffer.
  3. Defina os quatro uniforms float e envie p_ringColor e p_background decodificadas de sRGB para linear.
  4. Desenhe com o blending desligado, a partir de um laço de requestAnimationFrame, para o anel continuar se mexendo.

Uma página que só às vezes mostra o anel deve parar o laço enquanto ele estiver escondido; o shader roda para cada pixel do canvas a cada quadro, com o anel na tela ou não.

Monte no Godot

O shader exportado supõe o HDR 2D desligado (o padrão). Com ele ligado, faça u_toLinear e u_toSrgb devolverem a entrada sem mudança.

O código copiado é um shader canvas_item completo; i.time lê o TIME do Godot.

  1. Adicione um ColorRect e defina o preset de âncoras como Full Rect.
  2. Defina o Material dele como um novo ShaderMaterial com um novo Shader e cole o código.
  3. Ajuste o raio, a espessura da linha, a amplitude do pulso, a velocidade e as cores em Shader Parameters. O anel também se anima no editor.

Num VisualShader, o raio é a entrada Time vezes a velocidade, passando por Sin, vezes a amplitude, mais o raio. Subtraia esse valor do Length2D da UV centralizada, tire o Abs e recorte com SmoothStep em torno da espessura da linha; depois OneMinus e Mix entre as cores.

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

pulsing-ring.fragGLSL
#version 300 es// Circles, boxes & lines → Pulsing ring, 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_radius; // Radius: 0.5 (0.1 to 0.9)uniform float p_width; // Line width: 0.04 (0.005 to 0.3)uniform float p_pulse; // Pulse size: 0.1 (0.0 to 0.4)uniform float p_speed; // Speed: 3.0 (0.0 to 10.0)uniform vec3 p_ringColor; // Ring color: vec3(0.35, 0.85, 1.0), sRGB; upload it decoded to linearuniform vec3 p_background; // Background: vec3(0.05, 0.06, 0.1), sRGB; upload it decoded to linearin 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));}vec4 effect(Inputs i) {	vec2 p = (i.uv * 2.0 - 1.0) * vec2(i.resolution.x / i.resolution.y, 1.0);	float d = length(p);	// sin swings between -1 and 1, so the radius breathes by p_pulse either side of p_radius	float pulsed = p_radius + p_pulse * sin(i.time * p_speed);	// abs() measures from the circle's line on both sides: the distance to a ring, not to a disc	float band = abs(d - pulsed);	float pixel = 2.0 / i.resolution.y;	float ring = 1.0 - smoothstep(p_width - pixel, p_width + pixel, band);	vec3 color = mix(p_background, p_ringColor, ring);	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);}
pulsing-ring.gdshaderShader do Godot
// Circles, boxes & lines → Pulsing ring, Godot 4. From Creative Center, MIT license.shader_type canvas_item;// Unshaded shows the effect's color as is, like the site's preview; scene lights don't touch itrender_mode unshaded;uniform float radius : hint_range(0.1, 0.9, 0.01) = 0.5;uniform float width : hint_range(0.005, 0.3, 0.005) = 0.04;uniform float pulse : hint_range(0.0, 0.4, 0.01) = 0.1;uniform float speed : hint_range(0.0, 10.0, 0.1) = 3.0;uniform vec3 ringColor : source_color = vec3(0.35, 0.85, 1.0);uniform vec3 background : source_color = vec3(0.05, 0.06, 0.1);struct Inputs {	vec2 uv;	vec3 position;	vec3 normal;	vec3 view;	float time;	vec2 fragCoord;	vec2 resolution;};// Godot's 2D renderer works in sRGB (HDR 2D off, the default), so the frame decodes color params and sRGB// textures to linear itself and encodes the result. With HDR 2D on, make these return c unchanged.vec3 u_toLinear(vec3 c) {	return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3(2.4)), step(0.04045, c));}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));}vec4 effect(Inputs i) {	vec2 p = (i.uv * 2.0 - 1.0) * vec2(i.resolution.x / i.resolution.y, 1.0);	float d = length(p);	// sin swings between -1 and 1, so the radius breathes by pulse either side of radius	float pulsed = radius + pulse * sin(i.time * speed);	// abs() measures from the circle's line on both sides: the distance to a ring, not to a disc	float band = abs(d - pulsed);	float pixel = 2.0 / i.resolution.y;	float ring = 1.0 - smoothstep(width - pixel, width + pixel, band);	vec3 color = mix(u_toLinear(background), u_toLinear(ringColor), ring);	return vec4(color, 1.0);}void fragment() {	Inputs i;	// UV starts at the top-left; the effect expects GLSL's bottom-left	i.uv = vec2(UV.x, 1.0 - 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 = 1.0 / SCREEN_PIXEL_SIZE;	i.fragCoord = vec2(FRAGCOORD.x, i.resolution.y - FRAGCOORD.y);	vec4 frag_out = effect(i);	COLOR = vec4(u_toSrgb(clamp(frag_out.rgb, 0.0, 1.0)), 1.0);}

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