基本的にはUnityの機能をそのまま使うのでいいかなと思います。
→詳しくはこちらを参照ください(カメラに写っている間のみ処理を行う)
使えるメソッド
- OnBecameVisible
- OnBecameInvisible
これらは、可視/不可視状態になった際に呼ばれるMonoBehaviourのコールバックです。
ただ、シーンエディタ上のカメラで撮影された場合にも呼ばれるとのことで、デバッグ環境では若干使いづらいかもしれません。
その場合に使えるのが
- OnWillRenderObject
です。
これは、撮影されるカメラごとに一度呼ばれるコールバックで、Camera.current
で、現在撮影中のカメラの情報にアクセスすることができます。
ここで、SceneCamera
を除外することで、上記の問題であるシーンエディタ上でのデバッグも問題なく行える、というわけです。
さて、以上がUnity上のサイクルによる処理ですが、自前で計算をしてふんわり処理を行いたい場合は、以下のようにマトリクスをうまく使ってやることで処理が可能です。
概要
普通に、プロジェクション変換まで座標を持っていって計算する方法です。
要はクリッピング領域内外の判定をCPUで行う、ということですね。
行列計算
Matrix4x4 V = _camera.worldToCameraMatrix;
Matrix4x4 P = _camera.projectionMatrix;
Matrix4x4 VP = P * V;
こんな感じで各種マトリクスを取得することができます。
それを先に VP
行列として計算しておきます。
これに、自身の transform.position
をかけてあげればプロジェクション空間に座標が変換されます。
デバイス正規化座標系への変換
続いて、計算されたベクトルから w
除算を経て、デバイス正規化座標系へ変換します。
これにより、すべての x, y, z
座標は -1.0 ~ 1.0
の範囲に正規化されます。
(逆にこの値に収まっていない場合は、計算中の点が視錐台の外にあることを示しています)
var p = transform.position;
Vector4 pos = VP * new Vector4(p.x, p.y, p.z, 1.0f);
if (pos.w == 0)
{
IsInView = true;
return;
}
float x = pos.x / pos.w;
float y = pos.y / pos.w;
float z = pos.z / pos.w;
あとは、計算された x, y, z
それぞれの値を比較し、-1.0 ≦ x(y, z) ≦ 1.0
の範囲かを調査して、範囲外だった場合は表示されていない、ということにすれば目的達成です。
ただ、これはあくまでカメラの視錐台内に入っているか、を計算しているに過ぎないので、仮に前面になにか大きなオブジェクトがあって視界を覆っていても、計算上は「見えている」ことになる点に注意が必要です。
(ただそれは、Unityのコールバックも同様です)
if (x < -1.0f)
{
IsInView = false;
return;
}
if (x > 1.0f)
{
IsInView = false;
return;
}
if (y < -1.0f)
{
IsInView = false;
return;
}
if (y > 1.0f)
{
IsInView = false;
return;
}
if (z < -1.0f)
{
IsInView = false;
return;
}
if (z > 1.0f)
{
IsInView = false;
return;
}
IsInView = true;
あとは、この IsInView
の変化を監視して、true
だったらカメラに映っている、false
だったら映っていないとして処理を分岐してあげればOKです。