Wie stelle ich sicher, dass perspektivische und orthografische Projektionsmatrizen Objekte gleicher Größe auf dem Bildschirm anzeigen?

7

Ich arbeite an einem 3D-Szeneneditor und möchte die Szene in orthographischer Projektion zeigen. Mein aktuelles Problem ist, dass ich nicht sicher bin, wie ich die orthografische Projektionsmatrix so berechnen soll, dass die Objekte nicht in einer völlig anderen Größe auf dem Bildschirm angezeigt werden.

Ich habe derzeit die folgenden 2 Funktionen zur Berechnung der Kameraprojektionsmatrix.

Matrix4.createPerspective = function(fov, aspect, near, far, result) {
    if (typeof result === 'undefined') {
        result = new Matrix4();
    }

    var ymax = near * Math.tan(fov * Math.PI / 360);
    var ymin = - ymax;
    var xmin = ymin * aspect;
    var xmax = ymax * aspect;

    Matrix4.createFrustum(xmin, xmax, ymin, ymax, near, far, /*out*/ result);

    return result;
};

Matrix4.createOrthographic = function(left, right, top, bottom, near, far, result) {
    if (typeof result === 'undefined') {
        result = new Matrix4();
    }

    var re = result.elements;
    var w = right - left;
    var h = top - bottom;
    var p = far - near;

    var x = ( right + left ) / w;
    var y = ( top + bottom ) / h;
    var z = ( far + near ) / p;

    re[0] =2/w;  re[4] = 0;   re[8]  = 0;   re[12] =-x;
    re[1] = 0;   re[5] =2/h;  re[9]  = 0;   re[13] =-y;
    re[2] = 0;   re[6] = 0;   re[10] =-2/p; re[14] =-z;
    re[3] = 0;   re[7] = 0;   re[11] = 0;   re[15] = 1;

    return result;
};

Und so werden sie eingesetzt:

    updateProjectionMatrix: function(entity) {
        var camera = entity.getComponent('Camera');
        if (camera.isDirty()) {
            camera.aspect = camera.width / camera.height;
            if (camera.isOrthographic()){
                Matrix4.createOrthographic(
                    0, 
                    camera.width, 
                    0, 
                    camera.height, 
                    camera.near, 
                    camera.far, 
            /*out*/ camera._projectionMatrix);
            } else {
                Matrix4.createPerspective(
                    camera.fov,
                    camera.aspect,
                    camera.near,
                    camera.far,
            /*out*/ camera._projectionMatrix);
            }
        }
    },

Hier ist ein Beispiel dafür, was ich in Blender erreichen möchte (F5 wechselt zwischen Ortho und Perspektive). Beachten Sie, dass beide Cubes in den Ansichtsfenstern ungefähr gleich groß zu sein scheinen:

Mixer Beispiel

CheerioBacon
quelle
Ziemlich sicher, dass Sie nur Ihre Brennweite auf konstant einstellen müssen. Dies wäre der Abstand vom Fluchtpunkt zur Projektionsebene.
RandyGaul
@RandyGaul Ich bin mir sicher, dass Sie das trotzdem meinen, aber es reicht aus, beide Entfernungen zu synchronisieren, anstatt sie zu reparieren.
Danijar

Antworten:

4

Erstens createPerspectivesieht Ihre Funktion nicht ganz richtig aus - vergleichen Sie die verwendeten Formeln gluPerspective.

Wenn Sie für beide Kameras dieselbe Ansichtsmatrix verwenden möchten, müssen Sie beim Einrichten der orthografischen Matrix 0, camera.width, 0, camera.heightwahrscheinlich nicht verwenden -0.5*camera.width, 0.5*camera.width, -0.5*camera.height, 0.5*camera.height. Dadurch bleibt die Ansicht um die Kameraposition zentriert, anstatt die untere linke Ecke der Ansicht an der Kameraposition zu platzieren.

Möglicherweise müssen Sie auch unterschiedliche Nah- / Fernwerte für die Orthokamera verwenden, je nachdem, ob in der Orthoansicht die Geometrie hinter der Kamera angezeigt werden soll (was einen negativen Wert in der Nähe der Ebene erfordern würde).

Als Nächstes müssen Sie dann berechnen camera.widthund camera.heightbasierend auf dem aktuell ausgewählten / fokussierten Objekt, wenn Sie in den orthografischen Modus wechseln. Sie möchten, dass diese Werte die Breite und Höhe des Kegelstumpfs in der Tiefe (Abstand entlang der Z-Achse des Kameraraums) des ausgewählten Objekts darstellen.

Wenn Sie eine gluPerspectiveperspektivische Matrix im Stil verwenden, repräsentieren die Komponenten [0, 0] und [1, 1] der Matrix 2 / Breite bzw. 2 / Höhe in einer Tiefe von 1,0 Einheiten von der Kamera. Die Breite und Höhe des Kegelstumpfes skalieren linear mit der Tiefe. Sie müssen sie also nur mit der Tiefe des fokussierten Objekts multiplizieren:

objectDepth = dot(objectPos - cameraPos, cameraViewVector);
camera.width = (2.0 / perspectiveMatrix[0]) * objectDepth;
camera.height = (2.0 / perspectiveMatrix[5]) * objectDepth;

Natürlich können Sie die perspektivischen Matrixkomponenten bei Bedarf auch anhand des Sichtfelds und des Seitenverhältnisses neu berechnen.

Nathan Reed
quelle
Sobald Sie die objectDepth haben, können Sie die ortho Breite und Höhe wie folgt berechnen: var ymax = Math.tan(camera.fov * Math.PI / 360); var xmax = ymax * camera.aspect; var width = objectDepth * xmax; var height = objectDepth * ymax;
zfedoran