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

Ponto suave

Um ponto redondo cuja borda vai de nítida a um brilho suave com um só controle.

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

Parâmetros

0.50
0.080
#ffcc59
#12141f

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. uv * 2.0 - 1.0MultiplyAdduv * 2.0 - 1.0uv * 2.0 - 1.0uv * 2.0 - 1.0
    UV
    Tempo

    01 Centralizar as coordenadas

  2. lengthLength2Dlengthlengthlength

    02 Distância até o centro

  3. smoothstepSmoothStepsmoothstepsmoothstepsmoothstep

    03 Borda suave

  4. mixMixmixmixmix

    04 Colorir

Como funciona

O ponto é a menor forma útil que um shader de fragmento consegue desenhar, e ele já contém toda a ideia de desenhar com matemática: medir a que distância cada pixel está de alguma coisa e decidir com um limiar.

Centralize as coordenadas. A UV do quad vai de 0 a 1, com a origem no canto inferior esquerdo. uv * 2.0 - 1.0 leva a origem para o meio e faz cada eixo ir de -1 a 1. Só que a prévia é mais larga do que alta, então uma unidade na horizontal ficaria mais comprida na tela do que uma unidade na vertical, e o círculo sairia oval. Multiplicar x pela proporção da tela (largura dividida pela altura) deixa a unidade com o mesmo comprimento nas duas direções.

Meça a distância. length(p) é o comprimento do vetor que vai do centro até o pixel: 0 no meio, crescendo por igual em todas as direções. Todos os pixels à mesma distância formam um círculo, então o ponto já está ali; só falta a borda.

Recorte a borda. Um limiar transforma a distância em cobertura. step(radius, d) passaria de dentro para fora num único pixel e, numa borda curva, isso deixa uma escadinha de pixels serrilhados. Um smoothstep de radius - soft até radius + soft vai de 0 a 1 aos poucos, e 1.0 - … inverte o resultado para o lado de dentro valer 1. Com uma suavidade grande, essa rampa é o brilho suave; com uma pequena, ela encolhe até virar uma borda sem serrilhado.

A suavidade nunca fica abaixo de um pixel: p cobre 2 unidades na altura da prévia, então 2.0 / altura é um pixel nessas unidades, e max() mantém a suavidade pelo menos desse tamanho, então a rampa de radius - soft a radius + soft sempre cobre pelo menos dois pixels. Sem isso, levar a Suavidade a 0 traz a borda serrilhada de volta.

Pinte. A cobertura é um número de 0 a 1, então ela pode escolher entre duas cores com mix: o fundo onde vale 0, a cor do ponto onde vale 1 e uma mistura ao longo da borda.

Os mesmos quatro passos fazem a maioria das formas 2D. Troque length(p) pela distância até uma caixa ou uma linha e o limiar continua igual. Use a cobertura como alfa em vez de misturar cores, e o ponto vira uma máscara sobre qualquer coisa.

Monte no GLSL

O código copiado é um shader de fragmento WebGL2 completo. Ele desenha sobre qualquer coisa que cubra o canvas e tenha UVs.

  1. Desenhe um retângulo que cubra o canvas: dois triângulos de -1 a 1 no espaço de recorte.
  2. No shader de vértice, escreva v_uv indo de 0 a 1 ao longo do retângulo, além de v_position, v_normal e v_view. O shader de fragmento lê os quatro, então o WebGL2 se recusa a ligar o programa se o shader de vértice não os escrever; para os três últimos, constantes bastam.
  3. A cada quadro, defina u_resolution com o tamanho do drawing buffer em pixels e u_time com os segundos desde o início. Este card só lê a resolução.
  4. Defina p_radius e p_softness e envie p_dotColor e p_background decodificadas de sRGB para linear (c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4) por canal).
  5. Desenhe com o blending desligado. O shader escreve uma cor opaca já codificada em sRGB, que um canvas WebGL2 padrão mostra como está.

Para usar o ponto como máscara, devolva disc como alfa e desenhe com gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA).

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.

  1. Adicione um ColorRect a uma cena 2D ou de interface e defina o preset de âncoras como Full Rect, para ele cobrir a viewport.
  2. No Inspector, defina Material como um novo ShaderMaterial, depois o Shader dele como um novo Shader, e cole o código no editor de shader.
  3. Ajuste o raio, a suavidade e as cores em Shader Parameters; o ponto se atualiza no editor.

Num VisualShader, a cadeia é UV, um MultiplyAdd (vezes 2, menos 1) e uma multiplicação pela proporção, depois Length2D, SmoothStep com as bordas no raio menos e mais a suavidade, um OneMinus e um Mix entre as duas 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

soft-dot.fragGLSL
#version 300 es// Circles, boxes & lines → Soft dot, 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.05 to 1.0)uniform float p_softness; // Softness: 0.08 (0.0 to 0.5)uniform vec3 p_dotColor; // Dot color: vec3(1.0, 0.8, 0.35), sRGB; upload it decoded to linearuniform vec3 p_background; // Background: vec3(0.07, 0.08, 0.12), 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) {	// Centered, with x scaled by the aspect ratio: one unit is as long across as up, so the dot stays round	vec2 p = (i.uv * 2.0 - 1.0) * vec2(i.resolution.x / i.resolution.y, 1.0);	float d = length(p);	// p spans 2 units over the height, so 2 / height is one pixel: softness never drops below it, so the ramp	// always spans at least two pixels and never stair-steps	float soft = max(p_softness, 2.0 / i.resolution.y);	float disc = 1.0 - smoothstep(p_radius - soft, p_radius + soft, d);	vec3 color = mix(p_background, p_dotColor, disc);	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);}
soft-dot.gdshaderShader do Godot
// Circles, boxes & lines → Soft dot, 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.05, 1.0, 0.01) = 0.5;uniform float softness : hint_range(0.0, 0.5, 0.005) = 0.08;uniform vec3 dotColor : source_color = vec3(1.0, 0.8, 0.35);uniform vec3 background : source_color = vec3(0.07, 0.08, 0.12);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) {	// Centered, with x scaled by the aspect ratio: one unit is as long across as up, so the dot stays round	vec2 p = (i.uv * 2.0 - 1.0) * vec2(i.resolution.x / i.resolution.y, 1.0);	float d = length(p);	// p spans 2 units over the height, so 2 / height is one pixel: softness never drops below it, so the ramp	// always spans at least two pixels and never stair-steps	float soft = max(softness, 2.0 / i.resolution.y);	float disc = 1.0 - smoothstep(radius - soft, radius + soft, d);	vec3 color = mix(u_toLinear(background), u_toLinear(dotColor), disc);	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