Wie erstelle ich ein Weitwinkel- / Fischaugenobjektiv mit HLSL?

29

Welche Konzepte müssen umgesetzt werden, um die Wirkung eines Weitwinkelobjektivs mit unterschiedlichen Extremitäten zu erzielen?

Pseudocode und spezifische Erläuterungen zu den verschiedenen Phasen der Inhaltspipeline sowie zu den Informationen, die vom Quellcode an HLSL weitergegeben werden müssen, wären sehr nützlich.

Was sind die Unterschiede zwischen der Implementierung eines Weitwinkelobjektivs und einem Fischauge?

SirYakalot
quelle

Antworten:

37

Ein Weitwinkelobjektiv sollte sich nicht anders verhalten als andere normale Objektivmodelle. Sie haben nur ein größeres FOV (in dem D3DXMatrixPerspectiveFovLHSinne - ich gehe davon aus, dass Sie DirectX verwenden) oder größere Werte für links / rechts und unten / oben (im OpenGL- glFrustumSinne).

Ich glaube, der wirklich interessante Teil liegt in der Modellierung des Fischaugenobjektivs. Es gibt Fisheye Quake , das Sie studieren können, es kommt mit Quelle.

Die wahre Fischaugenprojektion

Die Projektion eines Fischaugenobjektivs ist jedoch stark nichtlinear. In der allgemeineren (meines Wissens auf Überwachungskameras beschränkten) Art von Linse wird ein Punkt Mim Raum auf die Oberfläche einer Einheitshalbkugel projiziert, dann wird diese Oberfläche parallel auf die Einheitsscheibe projiziert:

           M
             x                 M: world position
              \                M': projection of M on the unit hemisphere
               \  ______       M": projection of M' on the unit disc (= the screen)
             M'_x'      `-.
             ,' |\         `.
            /   | \          \
           /    |  \          \
          |     |   \          |
__________|_____|____\_________|_________
                M"    O        1

Es gibt andere Fisheye-Mappings , die interessantere Effekte erzielen können. Es liegt an dir.

Ich sehe zwei Möglichkeiten, um den Fisheye-Effekt in HLSL zu implementieren.

Methode 1: Führen Sie die Projektion auf dem Vertex-Shader durch

Vorteil : Im Code muss fast nichts geändert werden. Der Fragment Shader ist sehr einfach. Eher, als:

...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...

Du machst so etwas (kann wahrscheinlich sehr vereinfacht werden):

...
// This is optional, but it computes the z coordinate we will
// need later for Z sorting.
float4 out_Point = mul(in_Point, worldViewProjMatrix);

// This retrieves the world position so that we can project on
// the hemisphere. Normalise this vector and you get M'
float4 tmpPoint = mul(in_Point, worldViewMatrix);

// This computes the xy coordinates of M", which happen to
// be the same as M'.
out_Point.xy = tmpPoint.xy / length(tmpPoint.xyz);
...

Nachteile : Da die gesamte Rendering-Pipeline für lineare Transformationen gedacht war, ist die resultierende Projektion für Scheitelpunkte genau, aber alle Abweichungen sowie Texturkoordinaten sind falsch, und Dreiecke werden weiterhin als Dreiecke angezeigt, obwohl sie verzerrt erscheinen sollten.

Problemumgehungen : Es kann akzeptabel sein, eine bessere Annäherung zu erhalten, indem eine verfeinerte Geometrie mit mehr Dreiecksunterteilungen an die GPU gesendet wird . Dies kann auch in einem Geometrie-Shader durchgeführt werden. Da dieser Schritt jedoch nach dem Vertex-Shader ausgeführt wird, ist der Geometrie-Shader recht komplex, da er seine eigenen zusätzlichen Projektionen ausführen muss.

Methode 2: Führen Sie die Projektion auf dem Fragment-Shader durch

Eine andere Methode wäre, die Szene mit einer Weitwinkelprojektion zu rendern und dann das Bild zu verzerren, um einen Fisheye-Effekt mit einem Vollbild-Fragment-Shader zu erzielen.

Wenn der Punkt MKoordinaten (x,y)auf dem Fischaugenbildschirm hat, bedeutet dies, dass er Koordinaten (x,y,z)auf der Hemisphäre hat, mit z = sqrt(1-x*x-y*y). Das heißt, es wurden Koordinaten (ax,ay)in unserer Szene mit einem thetasolchen FOV gerendert a = 1/(z*tan(theta/2)). (Nicht 100% sicher über meine Mathematik hier, ich werde heute Abend noch einmal überprüfen).

Der Fragment-Shader wäre also etwa so:

void main(in float4 in_Point : POSITION,
          uniform float u_Theta,
          uniform sampler2D u_RenderBuffer,
          out float4 out_FragColor : COLOR)
{
    z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y);
    float a = 1.0 / (z * tan(u_Theta * 0.5));
    out_FragColor = tex2D(u_RenderBuffer, (in_Point.xy - 0.5) * 2.0 * a);
}

Vorteil : Sie erhalten eine perfekte Projektion ohne Verzerrungen, abgesehen von denen aufgrund der Pixelgenauigkeit.

Nachteil : Sie können nicht die gesamte Szene physisch betrachten, da das Sichtfeld nicht 180 Grad erreichen kann. Je größer das FOV, desto schlechter die Präzision in der Bildmitte ... genau dort, wo Sie maximale Präzision wünschen.

Problemumgehungen : Der Genauigkeitsverlust kann durch Ausführen mehrerer Rendering-Durchläufe (z. B. 5) und Ausführen der Projektion nach Art einer Cube-Map verbessert werden. Eine weitere sehr einfache Lösung besteht darin, das endgültige Bild einfach auf das gewünschte Sichtfeld zuzuschneiden. Auch wenn das Objektiv selbst ein 180-Grad-Sichtfeld hat, möchten Sie möglicherweise nur einen Teil davon rendern. Dies wird als "Vollbild" -Fisheye bezeichnet (was etwas ironisch ist, da es den Eindruck erweckt, dass Sie das "Vollbild" erhalten, während es das Bild tatsächlich beschneidet).

(Hinweis: Wenn Sie dies nützlich, aber nicht klar genug fanden, teilen Sie mir dies bitte mit. Ich würde gerne einen ausführlicheren Artikel darüber schreiben.)

sam hocevar
quelle
sehr nützlich, und ich würde den ausführlicheren Artikel begrüßen, den Sie ganz herzlich schreiben möchten!
SirYakalot
Wäre es möglich, beide Ansätze zu kombinieren, um bessere Ergebnisse zu erzielen? Führen Sie zuerst eine Projektion in VS durch, um alles im Blick zu haben, und entfernen Sie dann die Projektion in PS und projizieren Sie erneut, um korrekte UVs und alles zu erhalten? Es kann erforderlich sein, einige weitere Parameter an PS zu senden, um die Projektion auf das Original korrekt rückgängig zu machen.
Ondrej Petrzilka
3

Es sollte sein z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y), richtig?

Meine GLSL-Implementierung ist:

#ifdef GL_ES
precision mediump float;
#endif

varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float fovTheta; // FOV's theta

// fisheye
void main (void)
{   
    vec2 uv = v_texCoord - 0.5;
    float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y);
    float a = 1.0 / (z * tan(fovTheta * 0.5));
//  float a = (z * tan(fovTheta * 0.5)) / 1.0; // reverse lens
    gl_FragColor = texture2D(u_texture, (uv* a) + 0.5);
}
bman
quelle
hey @Josh, wie wurde fovTheta berechnet?
Tom
1
Ich habe diese Antwort nur bearbeitet, um die Formatierung anzupassen. Ich glaube, Sie möchten @bman direkt ansprechen.
Josh