今回は、3Dの見た目に「ツヤ」を与えるスペキュラ(鏡面反射)について解説していきます。
「拡散反射(ランバート)は分かったけど、スペキュラって何?」
「フォン反射とブリン・フォン反射、何が違うの?」
「ハイライトの大きさってどうやって変えるの?」
こんな悩みはありませんか?
スペキュラは、ひとことで言うと「光源が物の表面に映り込んだもの」です。拡散反射が「その物の色」を表すのに対し、スペキュラは「光そのものの色」が見えている状態です。
ハイライトの大きさは光沢度(Shininess)という1つの数値で決まります。実際に自作のDirectXプロジェクトで、光沢度だけを変えた球を並べてみました。

この記事では、スペキュラの仕組み・フォンとブリンフォンの違い・光沢度の意味・HLSL実装・ハマりどころを解説します。読み終えると、狙った質感のツヤを自分で作れるようになりますので、ぜひ最後まで読んでください。
スペキュラの仕組み|光が目に跳ね返ってくるか
拡散反射(ランバート)は、光が表面で四方八方に散る現象です。どこから見ても明るさが同じなので、見る角度によらず一定です。
一方スペキュラは、光が鏡のように跳ね返る現象です。跳ね返った光がちょうど目に入ってくる角度のときだけ明るく見えるので、見る角度によってハイライトの位置が動きます。物を傾けるとツヤの位置が移動するのは、このためです。
フォン反射|反射ベクトルで計算する
古典的な手法がフォン反射です。「光が反射した向き」と「視線の向き」がどれだけ近いかで、ハイライトの強さを決めます。
// フォン反射:反射ベクトル R と 視線ベクトル V の内積
float3 eyev = normalize(In.WorldPosition.xyz - CameraPosition.xyz);
float3 refv = normalize(reflect(Light.Direction.xyz, normal));
float specular = saturate(-dot(refv, eyev));
specular = pow(specular, 30); // 30 が光沢度直感的で分かりやすいのですが、reflectの計算が必要なぶん少し重く、また浅い角度でハイライトが不自然に切れることがあります。
ブリン・フォン反射|ハーフベクトルを使う
改良版がブリン・フォン反射です。反射ベクトルを求める代わりに、視線と光の「ちょうど中間の向き」(ハーフベクトル)を使います。
面の法線がこの中間方向を向いているとき、光はちょうど目に跳ね返ってきます。つまりハーフベクトルと法線が近いほど明るい、と考えるわけです。
// ブリン・フォン反射:ハーフベクトル H と 法線 N の内積
float3 eyeVector = normalize(CameraPosition.xyz - In.WorldPosition.xyz);
float3 lightVector = -normalize(Light.Direction.xyz);
// 視線と光の中間の向き
float3 halfVector = normalize(eyeVector + lightVector);
float specular = saturate(dot(halfVector, normal));
specular = pow(specular, shininess); // shininess が光沢度
// 最終色 = ベース色 ×(拡散 + 環境光)+ スペキュラ
outDiffuse.rgb = baseColor * (Light.Diffuse.rgb * light + Light.Ambient.rgb)
+ specular.xxx;ベクトルを足して正規化するだけなのでフォンより軽く、見た目も自然です。現在はこちらが主流で、今回の比較画像もブリン・フォンで描いています。
光沢度(Shininess)|ハイライトの鋭さ
pow()の指数が光沢度です。この数字が大きいほど、内積が1に近い部分だけが残るのでハイライトが小さく鋭くなります。
- 低い(4〜16)…広くぼんやり。ゴム、粘土、布のような質感
- 中くらい(30〜100)…プラスチック、塗装面
- 高い(200以上)…小さく鋭い点。磨いた金属、ガラス
なおPBRでは、この光沢度の代わりにラフネス(粗さ)という「逆向きの」パラメータを使います。考え方は同じで、ラフネスが低いほどハイライトが鋭くなります(詳しくはPBRのラフネスの記事へ)。
【重要】私がスペキュラでハマった体験談
チーム制作でキャラクターにツヤを入れたとき、暗い場所にいるのに、体だけが妙にテカって浮いて見えるという問題が起きました。ライトを弱めても、ハイライトだけは白く残ってしまいます。
原因はスペキュラにライトの色や強さを掛けていなかったことでした。+ specular.xxx と、そのまま白を足していたためです。+ specular * Light.Diffuse.rgb のようにライトの色と強さを反映させたら、暗い場所ではツヤも自然に暗くなりました。
「スペキュラも光の一種なので、光の強さに従わせる」と理解した出来事でした。
スペキュラ実装のよくある失敗例と対処法
①光が当たっていない裏側までツヤが出る
スペキュラだけを単独で計算すると、影の側にもハイライトが出ることがあります。specular *= NdotL のように拡散反射の明るさを掛けて、光が当たっていない面ではスペキュラも消えるようにしましょう。
②光沢度を0にしてしまう
pow(x, 0)は常に1になるため、物体全体が真っ白になります。光沢度は最低でも1以上にしてください。
③ハイライトがカクカクする
スペキュラは変化が急なので、頂点シェーダーで計算すると形が崩れます。必ずピクセルシェーダーで計算しましょう。理由は頂点ライティングとピクセルライティングの記事で詳しく解説しています。
注意点
- スペキュラにもライトの色と強さを掛ける
- 拡散反射の明るさを掛けて、裏側に出ないようにする
- スペキュラはピクセルシェーダーで計算する
まとめ
- スペキュラは光源が表面に映り込んだもの。見る角度で動く
- フォンは反射ベクトル、ブリン・フォンはハーフベクトルを使う
- ブリン・フォンの方が軽くて自然なので現在は主流
- 光沢度が大きいほどハイライトは小さく鋭くなる
スペキュラを足すだけで、のっぺりした3Dモデルが一気に「物っぽく」なります。まずは光沢度を極端な値に振ってみて、質感がどう変わるか目で確かめてみてください。

