Drehen eines Vektors um einen anderen Vektor im Shader

8

Ich habe eine Geländefläche mit einer Normalen für jeden Punkt im Gelände.

Ich habe eine zweite Detail-Normalkarte, die auf das Gelände angewendet werden soll.

  • Diese Normalen liegen im 3-Raum.

  • Der Y-Wert beider Normalen ist immer positiv.

  • Die X-, Z-Werte beider Normalen können positiv / negativ / null sein.

  • Der 1. Normalvektor (blau), um den wir den 2. Vektor (orange) drehen, kann fast horizontal sein.

Ich bin mit einer ungefähren Lösung einverstanden, wenn sie das Berechnen einfacher / schneller macht. Bild der normalen normalen blauen Oberfläche, der normalen normalen Kartenkarte und des normalen gewünschten grünen Ergebnisses.

Im obigen Bild sehen Sie die blaue Oberflächennormale (von der 1. Normalkarte), die orange Normale Normalen (von der 2. Normalkarte) und die grüne gewünschte Ergebnisnormale.

Der Betrag, um den der orangefarbene Vektor gedreht wird, sollte ungefähr (oder wenn möglicherweise genau) dem Winkel entsprechen, den der blaue Normalenvektor mit der XZ-Ebene bildet (wobei Y oben ist, wie das DirectX-Koordinatensystem).

Hier ist ein zweites Szenario:

Die blaue Oberflächennormale ist fast horizontal, daher wird die 2. Normalkarte auf eine fast vertikale Fläche angewendet, sodass der orangefarbene Normalkartenvektor weiter gedreht wird

In dem obigen Bild ist die blaue Oberflächennormale fast horizontal, so dass die 2. Normalkarte auf eine fast vertikale Fläche angewendet wird, wodurch der orangefarbene Normalkartenvektor weiter gedreht wird.

Die Rotation wird in einem HLSL-Shader implementiert.

Wie drehe ich die 1. orange Normale basierend auf der Richtung der 2. blauen Normalen?

Edit: Vielleicht brauche ich eine Tangente und Bitangente sowie die normale?

So komme ich zum Normalen:

float4 ComputeNormals(VS_OUTPUT input) : COLOR
{
    float2 uv = input.TexCoord;

    // top left, left, bottom left, top, bottom, top right, right, bottom right
    float tl = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1, -1)).x);
    float  l = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1,  0)).x);
    float bl = abs(tex2D(HeightSampler, uv + TexelSize * float2(-1,  1)).x);
    float  t = abs(tex2D(HeightSampler, uv + TexelSize * float2( 0, -1)).x);
    float  b = abs(tex2D(HeightSampler, uv + TexelSize * float2( 0,  1)).x);
    float tr = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1, -1)).x);
    float  r = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1,  0)).x);
    float br = abs(tex2D(HeightSampler, uv + TexelSize * float2( 1,  1)).x);

    // Compute dx using Sobel filter.
    //           -1  0  1 
    //           -2  0  2
    //           -1  0  1
    float dX = tr + 2*r + br - tl - 2*l - bl;

    // Compute dy using Sobel filter.
    //           -1 -2 -1 
    //            0  0  0
    //            1  2  1
    float dY = bl + 2*b + br - tl - 2*t - tr;

    // Compute cross-product and renormalize
    float3 N = normalize(float3(-dX, NormalStrength, -dY));    

    // Map [-1.0 , 1.0] to [0.0 , 1.0];
    return float4(N * 0.5f + 0.5f, 1.0f);
}

Wie könnte ich dann die Tangenten- und Bitangensvektoren erhalten?

Reicht es aus, das Kreuzprodukt der Normalen mit dem Einheitsvektor der Z-Achse zu nehmen, um den Tangentenvektor zu finden? (Da das normale.Y immer positiv ist, wo Y oben ist und Z in Ihren Bildschirm zeigt).

Und dann nimm diesen Tangentenvektor und kreuze ihn mit der Normalen, um den Bitangens zu erhalten?

Und dann nehmen Sie die Normalen, Tangenten und Bitangens zusammen, um eine Rotationsmatrix zu bilden, um den orangefarbenen normalen Kartenvektor zu drehen?

Selbst wenn das funktioniert, scheint dies eine Menge Berechnung für einen Pixel-Shader zu sein. Funktioniert das und gibt es einen effizienteren Weg?

Bearbeiten:

Dieses Bild kann Ihnen helfen zu verstehen, was ich versuche zu tun: Normale Zuordnung.

Wenn Sie wissen, was normales Mapping ist, sollte dies meiner Meinung nach unkompliziert sein.

Ich versuche, die normale Karte, die verschiedene Normalen enthält, auf eine Oberfläche anzuwenden. Die Oberfläche hat ihre eigenen Normalen. Die normale Karte enthält viel mehr Normalen als die Oberfläche, sodass mehrere normale Kartennormalen über einen einzelnen Teil der Oberfläche abgetastet werden, der nur eine einzige Norm enthält.

Olhovsky
quelle
Ihre Frage macht keinen Sinn. Sie können einen Vektor drehen durch einen Vektor (können Sie drehen um einen Vektor, und in der Tat Sie müssen eine Drehachse in 3-Raum angeben). Sie können den Winkel finden, den ein Vektor mit einer Ebene bildet. Im Fall einer Oberflächennormalen wird sie jedoch als 90 Grad definiert . Darüber hinaus macht das Drehen eines Vektors um 90 Grad keinen Sinn, es sei denn, Sie geben, wie bereits erwähnt, die Rotationsachse an.
Andrew Russell
Die Oberflächennormale beträgt 90 Grad von der Oberfläche und einen anderen Winkel von der XZ-Ebene (wobei Y nach oben und Z nach oben zum Bildschirm zeigt, wie in Direct X). Ich möchte den Vektor um den Winkel drehen, den die Normalen mit der XZ-Ebene bilden.
Olhovsky
Ich versuche, eine normale Karte auf eine Geländeoberfläche anzuwenden. Ich denke, eine Möglichkeit besteht darin, den Tangenten- und Bitangensvektor zu finden und dann diejenigen mit der Oberflächennormalen zu verwenden, um eine Matrix zu bilden, die ich mit der orangefarbenen Normalkartennormalen multipliziere, um die grüne Ergebnisnormalen zu erhalten. Ich bin mir nicht sicher, wie ich die Bitangens und Tangenten finden soll oder ob es einen einfacheren Weg gibt. Ich habe meine Frage aktualisiert, um zu zeigen, wie ich aus dem Gelände das Normale bekomme.
Olhovsky
Ich habe meiner Frage ein weiteres Bild hinzugefügt, um klarer zu machen, was ich versuche. Nichts wirklich Besonderes, nur normales Mapping.
Olhovsky
Ihr aktuelles Diagramm ist viel sinnvoller.
Andrew Russell

Antworten:

4

Ich glaube, ich verstehe Ihre Frage, obwohl der Titel und die beiden ersten Bilder verwirrend sind.

Das Problem ist, dass eine Normalkarte auf einer xz-Ebene (dem orangefarbenen Vektor) abgebildet werden muss. Nun ist die durch die blaue Normale beschriebene tatsächliche Ebene nicht unbedingt in der xz-Ebene, so dass man eine Drehung von der xz-Ebene zur tatsächlichen Ebene finden muss, um den orangefarbenen Vektor zu drehen, um ihn auf der tatsächlichen Ebene abzubilden.

Wie können wir das jetzt tun? Bacial müssen wir die Drehung finden, die die xz-Ebene zu der durch die blaue Normale beschriebenen Ebene dreht.

  1. Finden Sie die Rotationsachse, indem Sie das Kreuzprodukt der blauen Normalen und der Normalen aus der xz-Ebene {0, 1, 0} nehmen. Der orangefarbene Vektor muss um diese Achse gedreht werden.

    axis = N_xz cross N_blue
  2. Finden Sie den Drehwinkel, indem Sie Arccosin des Punktprodukts beider Normalen nehmen

    angle = acos(N_xz dot N_blue)
  3. Jetzt eine Rotationsmatrix erstellen aus diesen Werten und die orangefarbenen Normal mit dieser Matrix - Transformation.

Maik Semder
quelle
Was Sie beschreiben, könnte funktionieren, aber ich bin es leid, all das in einem Shader zu tun, 920000 Mal pro Frame (dh einmal pro Pixel). Kann ich einfach eine Matrix aus Normal, Kreuz (normal, float3 (1,0,0)) und Kreuz (normal, float3 (0,0,1)) bilden? Ich versuche das jetzt, obwohl es nicht zu funktionieren scheint.
Olhovsky
Ich habe meiner Frage ein Bild hinzugefügt, um klarer zu machen, was ich versuche. Nichts wirklich Besonderes, nur normales Mapping.
Olhovsky
Sie können diese Matrix einmal pro Ebene im Vertex-Shader berechnen und an den Pixel-Shader übergeben oder auf der CPU pro Ebene berechnen und diese Matrix an den Shader übergeben. Aber das ist optimierend, erst laufen lassen und verstehen, wie es geht, dann optimieren;)
Maik Semder
Ok, versuche es jetzt.
Olhovsky
Das funktioniert, +1. Vielen Dank. Es ist jedoch etwas langsam. Ich versuche herauszufinden, wie ich eine Quaternion für diese Rotationsmatrix speichern könnte, anstatt die blaue Normalen zu speichern, und dann die Normalen aus dieser Quaternion zu konstruieren. Weißt du, wie ich das machen könnte?
Olhovsky
2

Ich glaube, Sie haben Ihr Problem überlegt.

Um zur normalen Standardabbildung zurückzukehren, haben Sie normalerweise 3 Vektoren: die Oberflächennormale, einen Tangentenvektor senkrecht zu diesem und einen Kotangens senkrecht zu beiden. Zusammen bilden diese den Tangentenraum, in dem die normale Karte eine Richtung beschreibt. Bei diesem Setup müssen Sie nur jede gelesene Komponente der normalen Karte mit ihrer jeweiligen Tangentenraumachse multiplizieren und die Ergebnisse summieren.

Sie haben also ein Gelände und eine normale Karte, die Sie darauf anwenden möchten. Sie haben bereits herausgefunden, wie Sie die Delta-Höhe nehmen und eine Oberflächennormale erzeugen können. Sie suchen jetzt nach Tangente und Kotangens.

Was ist also eine Tangente? Aus der obigen Beschreibung geht hervor, dass es sich tatsächlich um die 3D-Richtung handelt, die von einer der UV-Achsen in Ihrer Texturabbildung beschrieben wird. Dafür wird es doch benutzt, oder? Wie finden Sie diese Richtung? Nun, angesichts eines beliebigen Netzes ist es ein wenig schwierig; Bei einem regelmäßigen Gittergelände ist es jedoch selbstverständlich: Wenn Sie ein reguläres Gitter mit regelmäßigen UV-Strahlen haben, sind die Tangenten- und Kotangensrichtungen nur die beiden achsenausgerichteten Kanten, die Ihren Scheitelpunkt verlassen.

Sie wissen also, wie hoch die Punkte um Sie herum sind. Sie können eingeben, wie weit diese Punkte im Flugzeug entfernt sind. Subtrahieren Sie diese von dem Punkt, an dem Sie sich befinden, normalisieren Sie und los geht's: Sofortiger Tangentenraum.

Chris Subagio
quelle
+1, weil dies ein hilfreicher Beitrag ist. Ich weiß, dass Sie eine Basismatrix bilden können, die uns in einen Tangentenraum verwandelt (was Sie meiner Meinung nach vorschlagen). Ich weiß nicht, wie ich es formen soll. Ihre Antwort spielt auf den Anfang an, zeigt aber nicht wirklich, wie es geht. Beispiel: "Sie können eingeben, wie weit diese Punkte in der Ebene entfernt sind. Subtrahieren Sie diese von dem Punkt, an dem Sie sich befinden, normalisieren Sie und los geht's: Sofortiger Tangentenraum." ist wirklich vage. Derzeit befinden sich meine normalen Karten nicht im Tangentenraum. Es ist 4 Uhr morgens hier, also werde ich das morgen noch einmal besuchen.
Olhovsky
0

Wenn Sie auch die Tangente haben, können Sie diese Funktion verwenden, um die gestoßene Normalen zu erzeugen, nach denen Sie suchen. Andernfalls können Sie in Zukunft plagiieren!

//------------------------------------------------------------------------------
// Transforms a normal map sample to world space.
//------------------------------------------------------------------------------


  float3 NormalSampleToWorldSpace(float3 normalMapSample, float3 unitNormalW, float3 tangentW)
    {
        // Uncompress each component from [0,1] to [-1,1].
        float3 normalT = 2.0f * normalMapSample - 1.0f;

        // Build orthonormal basis.
        float3 N = unitNormalW;
        float3 T = normalize(tangentW - dot(tangentW, N) * N);
        float3 B = cross(N, T);

        float3x3 TBN = float3x3(T, B, N);

        // Transform from tangent space to world space.
        float3 bumpedNormalW = mul(normalT, TBN);

        return bumpedNormalW;
    }
ErnieDingo
quelle
-1

Sie können die LERP-Funktion verwenden, die linear zwischen den beiden Vektoren interpoliert. Wir geben Ihnen eine schöne "gemittelte" Normalität.

http://msdn.microsoft.com/en-us/library/bb509618(VS.85).aspx

(Natürlich dreht sich das nicht wirklich, aber angesichts des orangefarbenen und blauen Vektors erhalten Sie den grünen Vektor.)

Roy T.
quelle
Vielen Dank. Der blaue Vektor kann jedoch etwa 30 Grad von der XZ-Ebene entfernt sein. In diesem Fall funktioniert Lerping nicht (es sei denn, ich vermisse etwas?). Ich erkenne Ihr Bild übrigens, Sie haben ein A * -Suchbeispiel für XNA geschrieben.
Olhovsky
Hey ja das bin ich :). Zu Ihrer Frage denke ich, dass Lerping immer noch funktioniert. Möglicherweise müssen Sie den Vektor jedoch neu normalisieren. Wenn es nicht funktioniert, können Sie einen kleinen Testfall (muss nicht in HLSL enthalten sein, XNAs Vektor hat auch Lerp) mit zwei Vektoren, einem erwarteten Ergebnis und einem Rückgabeergebnis, dann kann ich Ihnen möglicherweise mehr sagen .
Roy T.
Ich habe ein zweites Bild hinzugefügt, um zu veranschaulichen, warum Lerping nicht funktioniert. Die orangefarbene Normalkartennormale muss gedreht werden, damit sie auf die Oberfläche angewendet wird. Wenn Sie beispielsweise eine Oberflächennormale haben, die der orangefarbenen Normalen-Kartennormalen entspricht, aber beide gedreht werden, erhalten Sie durch Lerping in einer beliebigen Menge nur den gleichen Vektor, und es findet überhaupt keine Drehung statt.
Olhovsky