ことれいのもり

OpenSiv3DでBasicCamera3Dカメラの視錐台を描画する

はじめに

OpenSiv3Dでは、BasicCamera3Dを使って、カメラの位置を変更することができます。

しかし、このカメラには致命的な欠点があります。

それは、カメラがどの位置からどっち向きに表示しているのか分からない! ということです。

これを解決するために、この記事ではOpenSiv3Dで視錐台をデバッグ表示します。

視錐台とは?

デバッグ表示の前に、視錐台について説明します。

視錐台とは「カメラの見える範囲」のことです。

Unityとかのゲームエンジンでカメラを配置すると、カメラの向きと範囲が線で描画されるあれのことです。


視錐台についての記事は、探せばたくさんあるので、ここでは深く説明しません。

もっと詳しく知りたい!という方は調べてみてください。


個人的にオススメの記事です。

数学的にも分かりやすく説明されています。

【Unity】【数学】視錐台(Frustum)について(第1回)

とりあえずこの記事を見る上では、視錐台がどんな形なのか、ということが分かっていれば問題ありません。

視錐台を描画するコード

視錐台を描画するコードです。

CalculateCorner関数が面の四隅を求め、それをもとにMainループの中で描画します。


# include <Siv3D.hpp>

Array<Vec3> CalculateCorner(const Vec3& center, const Vec3& right, const Vec3& up, double width, double height)
{
    Vec3 halfRight = right * (width / 2);
    Vec3 halfUp = up * (height / 2);

    Vec3 topLeft = center + halfUp - halfRight;
    Vec3 topRight = center + halfUp + halfRight;
    Vec3 bottomLeft = center - halfUp - halfRight;
    Vec3 bottomRight = center - halfUp + halfRight;

    // 格納順は左上→右上→右下→左下にする(描画時にループで使いやすいから)
    return Array<Vec3>{topLeft, topRight, bottomRight, bottomLeft};
}

void Main()
{
    // BasicCamera3D を使って自由な視点操作を可能に
    BasicCamera3D battleCamera{ { 0, 3, -10 }, 10.0 };

    // 必要な情報
    double fovDegree = 60.0;    // 視野角
    double nearClip = 0.2;      // Near面までの距離
    double farClip = 50.0;      // Far面までの距離

    while(System::Update())
    {
        // カメラの情報を取得
        Vec3 eye = battleCamera.getEyePosition();       // カメラの位置
        Vec3 focus = battleCamera.getFocusPosition();   // 注視点
        Vec3 up = battleCamera.getUpDirection();        // 上方向

        // カメラの軸を求める
        Vec3 forward = (focus - eye).normalized();      // 前方向
        Vec3 right = forward.cross(up).normalized();    // 右方向
        Vec3 newUp = right.cross(forward).normalized(); // 上方向(計算でしっかり求めたバージョン)

        // アスペクト比
        Size sceneSize = Scene::Size();                 
        double aspect = static_cast<double>(sceneSize.x) / static_cast<double>(sceneSize.y);

        // Near・Far面の高さと幅を計算する
        double nearHeight = 2.0 * Math::Tan(fovDegree / 2.0) * nearClip;
        double nearWidth = nearHeight * aspect;
        double farHeight = 2.0 * Math::Tan(fovDegree / 2.0) * farClip;
        double farWidth = farHeight * aspect;

        // Near・Far面の中心点を計算する
        const Vec3 nearCenter = eye + forward * nearClip;
        const Vec3 farCenter = eye + forward * farClip;

        // Nar・Far面の角(四隅)を配列で格納する
        Array<Vec3> nearCorners = CalculateCorner(nearCenter, right, newUp, nearWidth, nearHeight);
        Array<Vec3> farCorners = CalculateCorner(farCenter, right, newUp, nearWidth, nearHeight);

        // 視錐台を線で描画
        for (int i = 0; i < 4; i++)
        {
            // Eye → NearCorner の線
            Line3D{ eye, nearCorners[i] }.draw(Palette::Orange);

            // NearCorner → FarCorner の線
            Line3D{ nearCorners[i], farCorners[i] }.draw(Palette::Orange);

            Line3D{ nearCorners[i], nearCorners[(i + 1) % 4] }.draw(Palette::Red);
            Line3D{ farCorners[i], farCorners[(i + 1) % 4] }.draw(Palette::Cyan);
        }

        // カメラの位置に赤い球を描画
        Sphere{ eye, 0.5 }.draw(ColorF{ 1.0, 0.0, 0.0, 0.5 });

        // カメラの注視点に青い球を描画
        Sphere{ focus, 0.3 }.draw(ColorF{ 0.0, 0.0, 1.0, 0.5 });
        }
}

視錐台のデバッグ描画

補足1: Near面の高さを求める式

// Near・Far面の高さと幅を計算する
double nearHeight = 2.0 * Math::Tan(fovDegree / 2.0) * nearClip;
double nearWidth = nearHeight * aspect;
double farHeight = 2.0 * Math::Tan(fovDegree / 2.0) * farClip;
double farWidth = farHeight * aspect;


この式は、Near面・Far面の高さを求めています。

カメラの位置・Near面の中心・Near面の高さの半分を結ぶ3つの辺を使うと、三角形ができます。

これらの情報を元にtanθを求める公式を使って高さを割り出します。


少し難しい式なので、この説明で分からないという方はこちらの記事をご覧ください!

おわりに

今回は、OpenSiv3DのBasicCamera3Dで視錐台をデバッグ表示する方法を紹介しました。

視錐台を可視化すると、「カメラがどこを向いていると、どこまで描画されるか」が明確に分かるようになります。

もしOpenSiv3Dのカメラ表示に困っている方がいたら、参考にしてみてください!