【ゲーム制作】リムライトとは?|逆光で輪郭を光らせるシェーダーの作り方

rim light thumbnail ゲーム制作

「キャラクターの輪郭だけがふわっと光る、あの演出はどうやるの?」「逆光のときだけ輪郭が光るのはなぜ?」「フレネル反射って言葉をよく聞くけど、リムライトと同じもの?」

こんな疑問はありませんか?

今回は、キャラクターやオブジェクトの輪郭を光らせるリムライト(縁光)について、実際にDirectXのシェーダーを書いて動かしながら解説していきます。理屈だけでなく、「なぜか全然光らない」を実際に踏んで解決した過程も含めて紹介します。

リムライトとは?

リムライトとは、

・視線に対してほぼ真横を向いている面(=輪郭付近)を、実際の光源とは関係なく光らせて縁取るシェーダー表現

です。逆光でキャラクターの輪郭だけがふわっと発光して見える、アニメ調の作品でよく使われる表現です。数学的にはフレネル反射(見る角度が浅いほど反射が強くなる現象)を単純化したものと言えます。

シェーダーの中身

ポイントピクセルライティングの計算に、リムライト用の項を1つ足すだけで実装できます。

// 1) どのくらい輪郭?
//   視線とピクセルの法線のなす角度が90度(直角)に近いほど輪郭
//   dot(eyeVector, normal) は 正面=1.0, 直角=0.0
//   よって「1.0 - dot」とすると 輪郭=1.0, 正面=0.0 になる
float lim = 1.0f - saturate(dot(eyeVector, normal));

// 2) どのくらい逆光?
//   視線ベクトルと光の逆ベクトル(=光からピクセルへの向き)の内積
//   逆光は視線と光の向きが同じになるので内積=1.0
//   順光は反対向きなので内積=-1.0
float lit = saturate(dot(eyeVector, lightDir));

// 輪郭の強さを調整(3乗にして際立たせる)
lim = pow(lim, 3.0f);

// リムライトの明るさ = 輪郭度 × 逆光度
float rim = lim * lit;

// 最終的な色に加算
outDiffuse.rgb += rim.xxx;

limは「視線に対してどれだけ真横を向いているか」、litは「そのピクセルが光源から見て逆光側にあるか」を表しています。この2つを掛け合わせることで、「輪郭であり、かつ逆光側」の部分だけが光るようになります。

通常のポイントライティング(PointPixelLightingPS.hlsl)との違いは、本当にこのrimを足す3行だけです。同じ球・同じライトで、リムライトあり/なしを実際に描画して比較してみます。

リムライトなしの球。輪郭がくっきり黒く落ちている
リムライトなし:輪郭までくっきり黒い
リムライトありの球。輪郭にうっすらとした光の縁取りが見える
リムライトあり:輪郭にうっすら光の縁取りが乗っている

なし版は輪郭までベタッと黒く落ちているのに対し、あり版は輪郭にうっすらとした明るい縁が乗っているのが分かります。

【重要】実際に踏んだ罠:計算は合っているのに全然光らない

この記事用に実装していて、地味にハマったことがあります。数式もコードも間違っていないはずなのに、最初にレンダリングした画像では、リムライトの効果がほぼ見えませんでした

原因を探るため、最終的な色の代わりにlimlitrimの値をそのままRGBとして出力するデバッグ表示に切り替えて確認しました。

// デバッグ用: R=lim, G=lit, B=rim をそのまま出力
outDiffuse.rgb = float3(lim, lit, rim);
outDiffuse.a = 1.0f;
return;

すると、球のほぼ全面が緑(litだけ高い)で、輪郭のごくごく際の1〜2ピクセルだけに赤とオレンジがにじんでいるのが見えました。計算そのものは正しく動いていて、ただ「輪郭とみなされる範囲」が通常のカメラ距離では画面上で1ピクセル未満にしかならず、事実上見えていなかっただけだったのです。

lim = pow(1 - dot(eye, normal), 3)は、視線と法線のなす角が本当にほぼ90度に近づかない限り小さい値のままです。球のように滑らかな形状だと、その「ほぼ90度」の範囲は、カメラが遠いほど画面上でごく狭い帯にしかなりません。

対処法は、カメラを球の半径に対してぐっと近づけることでした。同じ計算式・同じシェーダーのまま、カメラ距離を半径の2〜3倍から1.2倍程度まで近づけるだけで、輪郭の帯が画面上で数十ピクセル幅まで広がり、はっきり見えるようになりました。

リムライトのよくある失敗例と対処法

①効果が薄すぎて見えない

上で実演した通りです。まずはlimlitrimを直接色として出力し、本当に計算が動いているか確認しましょう。動いているのに見えないなら、カメラ距離やモデルの形状(角のある形は輪郭が急に変わるので、リムライトが乗りやすい)を疑います。

②光源の向きに関係なく常に輪郭が光ってしまう

litの項を掛け忘れてlimだけを加算すると、順光側の輪郭まで光ってしまいます。「輪郭度」と「逆光度」は必ず両方を掛け合わせましょう。

③pow乗数を上げすぎて何も見えなくなる

輪郭をシャープにしようとpow(lim, N)のNを大きくしすぎると、①と同じ理由でさらに帯が狭くなり、逆に見えなくなることがあります。Nは3前後から様子を見て調整するのがおすすめです。

注意点

  • リムライトは「輪郭度」×「逆光度」の掛け算
  • 効果が見えないときは値を直接色として可視化して確認する
  • 滑らかな形状(球など)は輪郭の帯が狭くなりがち
  • 光源はカメラの視線を延長したほぼ真後ろに置くと効果が乗りやすい

まとめ

  • リムライトは視線と法線のなす角(輪郭度)逆光度を掛け合わせて作る
  • 通常のライティング計算に3行足すだけで実装できる
  • 計算が合っていても、輪郭の帯が細すぎて見えないことがある(実際に踏んだ罠)
  • 迷ったら値を直接色として出力して可視化するのが一番早い

リムライトの数式自体はシンプルですが、「合っているはずなのに見えない」を実際に体験できたのは大きな収穫でした。うまく効果が出ないときは、疑う前にまず可視化してみる価値があります。

ここまで読んでくださりありがとうございました。

関連記事