手短な説明:
RayCastの結果のhitInfo.normalは「相手面の法線」を格納しているが、SphereCastが返すhitInfo.normalは違ってて
「球体上の交差点の位置によって変わる法線」である(ということがこのたび分かりました)
Making A Physics Based Character Controller In Unity (for Very Very Valet)
この動画を参考にしつつ、「Rigidbody+浮いたカプセルコライダー」を使ったキャラクターコントローラーを実装していました。
これは浮いたコライダーから下向きにレイを飛ばし、地面との距離に応じてリジッドボディを上向きに反発して浮かせることで成り立っています。ただしここで使っているのはRayCastではなく、SphereCastです。
RayCastは太さのない線を飛ばして衝突判定をとりますが、SphereCastは点の代わりに球を使い、太さのあるレイキャストをするような感じのものです。
それで、それを使ってキャラクターボディを浮かせていました(※ボディのコライダーは下半身がなく浮いてる状態です)
この左の画像・・・なんか分かりづらいですが、キャラクターコントローラーを使ってキャラクターを階段に立たせているところのスクリーンショットです。
水色の点(キャラクターのボディの中心)から、下向きにSphereCastをしています。
スフィアは階段の角に当たっているのですが、その結果の交点と法線(hitInfo)をギズモで可視化したのが紫の点。
点の位置はOK。でも法線の角度を見て「えっ!?」となりました。
上の画像では分かりづらいですが、キャラクターはこの階段の途中に立っています。
if (Physics.SphereCast(new Ray(rb.position, rb.transform.down), footRayRadius, out RaycastHit hitInfo, sphereCastMaxDistance, footLayerMask))
{
footCastHitPoint = hitInfo.point; // 交点
footCastHitNormal = hitInfo.normal; // 法線
…
↑交点と法線はhitInfoから取得。
RayCastのhitInfo.normalは「レイがヒットしたサーフェス」つまり相手オブジェクト(この場合は階段)の交差した面の法線を格納した変数だよね?
だとしたら左図のように、
階段の上の面と接触した判定ならA
階段の左の面と接触した判定ならB
のどちらかが返ってくるはずじゃないの?
と思ったわけです。それなのに法線がナナメになっているとはどういう了見か。
もしかして階段の角にめちゃめちゃ細い斜めのベベル面が挟まってて、それと接触しているのか?と思いましたがモデルをみてもそんなことはない。角は無駄なポリゴンのない完全な角でした。
それでフタを開けてみれば簡単な話でした。
最初に概略で述べたように、RayCastの結果のhitInfo.normalはリファレンスにある通り「レイがヒットしたサーフェスの法線」を返すのですが、
SphereCastの結果のhitinfo.normalはそうじゃなくて、なんかキャストした球体に対していい感じの法線を返してくる仕様らしいです。
実際はこんな法線を返してくる
RayCastもSphereCastも結果を得る型としてRaycastHit型を共用してるので、RayCast基準で書かれているリファレンスマニュアルの記述は
SphereCastに対しては罠になってしまうというわけでうえsn。
普通のRayCastと同じように相手の面の法線が欲しい場合、どうするのが「正解」なのかは分かりませんが
上の参考元フォーラムのスレ主の人はSphereCastの結果のナナメ法線(上の図の紫)の逆向きに通常のRayCastでレイを飛ばし、その結果のhitInfo.normalで相手面の法線を得ているようです。
(ただし相手がMeshColliderの場合に限りhitInfo.triangleIndexからMeshColliderポリゴンの該当面番号が得られるので、その面に対してCrossで法線を計算することでRayCastせずに済ませている)
相手面の法線を得るために追加でRayCastしないといけないなんてかなり計算コストの無駄なのでは、という話ですが
「レイキャストでキャストしたい相手コライダーがわかっている場合はPhysics.Raycast()の代わりにcollider.Raycast()を使えば、1つのオブジェクトとしか交差判定しなくていいので高速だよ」というアドバイスをしてくれてる人が上のスレッドにいました。
公式リファレンスSphereCastのページには、このような備考があります。
訳
注意:SphereCastは球体にオーバーラップしているコライダーを検出しません。太さ0のスフィアをキャストした場合、その結果は未定義でレイキャストと同じ結果になるとは限りません。
赤線で示した前半の部分はどういう意味でしょうか?
これに関してChatGPT(4o)に説明を求めたところ、わかりやすい答えを返してくれたのでやり取りをそのまま載せておきます。
質問:
UnityのSphereCastの公式リファレンスに「 NOTE:SphereCast will not detect colliders for which the sphere overlaps the collider.」(一部略)とありますがどういう意味ですか?
回答(ChatGPT4o):
Unityの公式リファレンスの「SphereCast will not detect colliders for which the sphere overlaps the collider」という文は、SphereCastの仕組みに関する注意点を説明しています。この文の意味は次の通りです:
SphereCastは、指定された方向に向かって「球体」を飛ばして衝突を検出するキャスト方法です。しかし、SphereCastは「キャストを開始する地点で既に球体が他のColliderに重なっている場合」、そのColliderとの衝突を検出しない、という挙動になります。
たとえば、以下のような場合です:
1. 球体のスタート位置が既にCollider内
SphereCastの開始位置が、対象のCollider(例えばBoxColliderやMeshColliderなど)の内部にある場合、そのColliderは衝突対象として認識されません。
2. 方向に沿った衝突のみを検出
SphereCastはあくまで、開始地点から指定した方向に向かって進む球体が「新たに」Colliderとぶつかるかをチェックします。開始地点で既に重なっているColliderは、キャスト方向に対する「新たな衝突」とはみなされないため、検出から外れるわけです。
この挙動は、SphereCastを使う際に開始位置や方向を調整して、重なり状態から開始しないようにすることで回避できます。
実はこれ、実際に遭遇したことがあったので「あーアレのことか!」と覚えていたのですが、スフィアキャストはこういう挙動になっています。
←開始点からスフィアレイを飛ばし、交差しているコライダーがあればその交点が得られます。
←しかしこのように、開始点から半径で伸びるスフィアに最初から交差しているコライダーは検出してくれません。
実際のスフィアキャストの検出範囲は下図のピンク色の範囲であるということですね。
「全体がSphereCast開始点の球エリアより外側にある」コライダーだけが検出対象になる
おわり