title: "円" description: "distance関数とlength関数を使って円を描きます。smoothstepによるアンチエイリアスも学びます。" chapter: "04-shapes" order: 2 challenge: description: "画面上に3つの異なるサイズの円を描いてください。それぞれ異なる色にし、円同士が一部重なるように配置しましょう。" hints:

  • "円のSDF関数を使い回し、中心位置と半径を変えて3回呼び出します。"
  • "smoothstep(0.01, 0.0, d) で各円の内側を判定できます。"
  • "色を重ねるには加算(+)が簡単です。重なり部分は色が混ざって明るくなります。"

distance関数とlength関数

GLSLには距離を計算するための便利な関数が用意されています:

// length: ベクトルの長さ(原点からの距離)
float len = length(vec2(3.0, 4.0)); // = 5.0

// distance: 2点間の距離
float dist = distance(vec2(1.0, 2.0), vec2(4.0, 6.0)); // = 5.0

実は distance(a, b)length(a - b) と同じです。

円のSDF

前回学んだ円のSDFをおさらいします:

float sdCircle(vec2 p, float r) {
    return length(p) - r;
}

任意の位置に円を描くには、座標をずらします:

vec2 center = vec2(0.5, 0.5);
float d = sdCircle(uv - center, 0.25);

smoothstepでアンチエイリアス

step を使うとくっきりした境界になりますが、ピクセルのジャギーが目立ちます。smoothstep を使うと滑らかな境界が得られます:

// step:ジャギーが出る
float shape = step(0.0, -d);

// smoothstep:滑らかな境界
float shape = smoothstep(0.01, 0.0, d);

smoothstep(edge0, edge1, x) は:

  • x <= edge0 のとき 0.0
  • x >= edge1 のとき 1.0(edge1 < edge0 の場合は逆)
  • その間は滑らかに補間

ピクセルサイズに応じた幅で smoothstep を使うと、解像度に依存しないアンチエイリアスが得られます:

float px = 1.0 / u_resolution.y; // 1ピクセルの大きさ
float shape = smoothstep(px, 0.0, d);

実践:円を描く

右のプレビューでは、画面中央に白い円が描かれています。コードを見てみましょう:

vec2 uv = gl_FragCoord.xy / u_resolution;
vec2 p = uv - vec2(0.5);
p.x *= u_resolution.x / u_resolution.y;

float d = length(p) - 0.3;
float circle = smoothstep(0.005, 0.0, d);

vec3 color = vec3(circle);

まとめ

  • length() でベクトルの長さ、distance() で2点間の距離を計算
  • 円のSDF:length(p - center) - radius
  • smoothstep を使うとアンチエイリアスの効いた滑らかな描画ができる