106. フレネル効果

カメラ空間座標を使用して、オブジェクトの縁が光るフレネル効果を実装します。

p5.js 2.0 WebGL p5.strands Shader GLSL
Learning Tutorial

フレネル効果

3D レンダリングで、オブジェクトの縁が光って見えたり、水面を見る角度によって反射が変わったりするのを見たことがあるかもしれません。これが フレネル効果 (Fresnel effect) です。この効果は、見る角度によって素材の見た目が変化する様子をシミュレートします。

フレネル効果を計算するには、形状のどの部分がカメラの反対側を向いているかを確認する必要があります。そのため、カメラ空間 (Camera space) で作業するのが便利です。カメラ空間(ビュー空間とも呼ばれます)では:

  • カメラは原点 (0, 0, 0) に配置されます
  • カメラは Z 軸の負の方向を向いています
  • すべての 3D 位置はカメラの視点からの相対的なものになります
NOTE
カメラ空間 カメラを (0, 0) とした仮想世界の相対的な見え方です。

この視点により、表面が視聴者にどのように見えるかを判断しやすくなります:

function fresnelCallback() {
  getCameraInputs((inputs) => {
    // 球面の法線ベクトル
    let normalVector = normalize(inputs.normal);
    // 表面からカメラに向かうベクトル
    let viewVector = normalize(-inputs.position);
    // ...
    return inputs;
  });
}

let viewVector = normalize(-inputs.position) という行を分解してみましょう:

  1. カメラ空間では、カメラは (0, 0, 0) にあります
  2. inputs.position は、処理中の現在の頂点の位置です
  3. これを反転させたもの (-inputs.position) は、頂点からカメラに向かうベクトルになります
  4. normalize() はこれを単位ベクトルに変え、距離に関係なく方向の計算に使いやすくします

フレネル係数の計算

フレネル効果の核心は、2 つの方向を比較することです:

  1. 表面の法線 (normal): 表面が「どちらを向いているか」
  2. 視線方向 (view direction): カメラが表面に対して「どこにあるか」
let base = 1 - dot(normalVector, viewVector);
let fresnel = pow(base, 2);

ドット積(内積)を使用して、これらの方向がどれだけ一致しているかを測定します。内積をとることで、以下がわかります:

  • 1 に等しい: 2 つのベクトルが同じ方向を向いている
  • 0 に等しい: 2 つのベクトルが垂直である
  • -1 に等しい: 2 つのベクトルが反対方向を向いている

表面がカメラを直接向いている地点では、法線と視線ベクトルがほぼ平行になり、ドット積は 1 に近づきます。球体の頂端のようなかすめるような角度では、ドット積は 0 に近くなります。

1 - dot(normalVector, viewVector) とすることで、この関係を反転させ、縁に近い部分が 1 に近づくようにします。これにより、オブジェクトの周囲にハイライトを作ることができます。

この値を pow(base, 2) で累乗することで、変化をより劇的にし、中央部分をより急速に暗くして縁の輝きを鋭くします。

色への適用

let col = mix([0, 0, 0], [1, 0.5, 0.7], fresnel);
inputs.color = [col, 1];

GLSL の mix() 関数は p5.js の lerp 関数に似ています。オブジェクトがカメラを向いていて fresnel 値が 0 に近いときは黒 [0, 0, 0] になり、縁に近づいて fresnel が 1 に近くなるとピンク色 [1, 0.5, 0.7] に補完されます。

getCameraInputs頂点シェーダー内にあるため、この時点では頂点の色を設定しています。フラグメントシェーダーが頂点間を補完して、ピクセルが連続して見えるようにします。

戻り値 [col, 1] に注目してください。シェーダー言語ではベクトルが主要なデータ型として優先されます。3 コンポーネントのベクトル (RGB) と float (アルファ) から、4 コンポーネントのベクトル (RGBA) を構成できます。

スケッチのソースコードを見てみましょう:

let fresnelShader;

function fresnelCallback() {
  const fresnelPower = uniformFloat(2);
  const fresnelBias = uniformFloat(-0.1);
  const fresnelScale = uniformFloat(4);

  getCameraInputs((inputs) => {
    let n = normalize(inputs.normal);
    let v = normalize(-inputs.position);
    let base = 1.0 - dot(n, v);
    let fresnel = fresnelScale * pow(base, fresnelPower) + fresnelBias;
    let col = mix([0, 0, 0], [1, 0.5, 0.7], fresnel);
    inputs.color = [col, 1];
    return inputs;
  });
}

async function setup() {
  createCanvas(400, 400, WEBGL);
  pixelDensity(1);
  fresnelShader = baseColorShader().modify(fresnelCallback);
}

function draw() {
  background(0);
  noStroke();
  orbitControl();
  shader(fresnelShader);
  sphere(80);
}
TIP
const mousePos = uniformVector2(() => [mouseX, mouseY]) をユニフォームとして送り、マウスの位置に基づいて色を変化させるなど、インタラクティブな効果を試してみてください。

微調整

ユニフォームを使用することで、結果を制御できます:

  • fresnelPower: 値が高いほど、中央から縁への変化が鋭くなります。
  • fresnelBias: 効果の中心をシフトさせます。
  • fresnelScale: 効果全体の強さを増幅させます。

License

Original URL: https://beta.p5js.org/tutorials/intro-to-p5-strands/

License: MIT License

Copyright (c) 2015-present p5.js contributors & The Processing Foundation