Wie kann ich ein dreieckiges Polygon in GLSL versetzen / verkleinern?

8

Ich muss alle (blauen) Dreiecke unabhängig voneinander mit dem Vertex-Shader versetzen. Um das Dreieck als Ganzes zu bearbeiten, habe ich benutzerdefinierte (vec3) Attribute für jeden Scheitelpunkt (rot) erstellt, der die nach links (lila) und rechts (grün) benachbarten Scheitelpunkte darstellt. Daraus muss ich den orangefarbenen Punkt in gleichem Abstand (im Bildschirmbereich ) von beiden angrenzenden Kanten ableiten . Mit drei solchen orangefarbenen Punkten, die von jedem Dreieck abgeleitet sind, wird das verarbeitete (orangefarbene) Dreieck an den Fragment-Shader weitergeleitet.

pro Scheitelpunktoperation versetzte Dreiecke

Im Idealfall wird das Dreieck ausgesondert (wie bei Backfacing / nicht gerendert), wenn die Offsets den verfügbaren Platz innerhalb des Dreiecks negieren, z. B. im zweiten Dreieck im zweiten Bild.

Ich verwende THREE.BufferGeometry () als meine Datenstruktur.

Hier ist ein Screenshot des Effekts, den ich anstrebe:

Geben Sie hier die Bildbeschreibung ein

Jackalope
quelle
Könnten Sie etwas mehr über den weiteren Kontext hinzufügen? Sollen die versetzten Dreiecke wie im ursprünglichen Netz angebracht bleiben? Bedeutet "ausgesondert", dass das ursprüngliche Dreieck verworfen wird oder nur, dass der Versatz aufgegeben wird und das Dreieck seine ursprüngliche Größe beibehält?
Trichoplax
1
Also ... wie funktioniert das mit Maschen? Denn in einem Netz hat ein Scheitelpunkt mehr als 2 Nachbarn. Oder ist das nur für einzelne Dreiecke?
Nicol Bolas
Meine Implementierung ist so, dass alle Dreiecke in einem kontinuierlichen Puffer angeordnet sind: [P1.x, P1.y, P1.z, P2.x, P2.y, P2.z ... Pn.x, Pn.y, Pn.z] mit benachbarten Punkten, die ebenfalls explizit an Attributen angeordnet sind. Auf diese Weise kann jeder Scheitelpunkt jeder Fläche berechnet und bearbeitet werden, ohne benachbarte Flächen zu beeinflussen. Nicol Bolas, ja, behandelt jedes Dreieck separat.
Jackalope
Trichoplax - "Keulen" bedeutet weggeworfen, nicht gerendert, wie bei einem nach hinten gerichteten, einseitigen Grundelement.
Jackalope
1
@Jackalope: " Sie scheinen beide zu behaupten , dass die GPU Gesichter als" an andere Gesichter gebunden " ansieht . " Das liegt daran, dass dies im Allgemeinen wahr ist. Die meisten Netze haben nicht nur benachbarte Dreiecke, die "identische Attribute" verwenden. Sie verwenden dieselben Eckpunkte wieder . Dies kann durch Dreieckslisten geschehen, die denselben Index mehrmals verwenden, oder durch Dreiecksstreifen oder was auch immer. Im Allgemeinen verwenden Netze jedoch benachbarte Scheitelpunkte wieder. Ihre Netze tun dies nicht, aber Ihr spezifischer Fall ändert nichts am allgemeinen Fall. Deshalb habe ich um Klarstellung gebeten.
Nicol Bolas

Antworten:

9

Wenn das Dreieck ▲ ABC gegeben ist, halbieren wir den Winkel ∠BAC mit der Linie AD, abgeleitet mit dem Satz der Winkelhalbierenden :

BA / BD = CA / CD Dreieck-Einschubdiagramm Punkt E repräsentiert unsere objektiv verfeinerte Position auf dem gewünschten resultierenden eingefügten Dreieck. Da es auf der Winkelhalbierenden AD liegt, ist es von den Seiten BA und CA gleich weit entfernt und bildet identische rechtwinklige Dreiecke ▲ AFE & ▲ AGE. Wir können jetzt Sinus für rechte Dreiecke verwenden , um die Länge von AE zu ermitteln:

AE = EG / Sin (∠EAG)

Das ist alles, was wir brauchen, also lasst uns GLSL kochen!

Wir beginnen mit allen typischen Attributen: Positions-, Normal- und Transformationsmatrizen. Da der Scheitelpunkt-Shader jedoch nur für einen einzelnen Scheitelpunkt funktioniert, müssen wir die benachbarten Scheitelpunkte als zusätzliche Attribute hinzufügen. Auf diese Weise findet jeder Scheitelpunkt seinen eigenen "Punkt E", wodurch das resultierende eingefügte Dreieck erzeugt wird. (Hinweis: Ich nenne sie hier nicht "B" und "C", da sie sich noch nicht im Bildschirmbereich befinden .)

    attribute vec3 left; //vertex to the left of this vertex
    attribute vec3 right; //vertex to the right of this vertex

Apropos Bildschirmbereich, ich beziehe auch das Seitenverhältnis des Displays ein (und mache es einheitlich, falls die Fenstergröße geändert wird).

Nachdem wir verschiedene Normalen für den Fragment-Shader vorbereitet und das Gesicht in einen Clipping-Bereich umgewandelt haben, können wir uns der Anwendung der obigen Mathematik widmen:

        attribute vec3 left; //vertex to the left of this vertex
        attribute vec3 right; //vertex to the right of this vertex
        uniform float aspect;
        varying vec3 vNormal;
        varying vec2 vUv;

        void main() {
            vNormal = normal;
            vUv = uv;

            mat4 xform= projectionMatrix * modelViewMatrix;
            vec4 A = xform * vec4( position, 1.0 );
            vec4 B = xform * vec4( left, 1.0 );
            vec4 C = xform * vec4( right, 1.0 );

            vec3 CB = C.xyz - B.xyz;
            vec2 BA = B.xy - A.xy;
            vec2 CA = C.xy - A.xy;
            float lengthBA = length(BA);
            float lengthCA = length(CA);
            float ratio = lengthBA / ( lengthBA + lengthCA );
            vec3 D = B.xyz + ratio * CB.xyz;
            vec3 AD = D - A.xyz;
            vec3 bisect = normalize(AD);

            float theta = acos( dot(BA, CA) / (lengthBA * lengthCA) ) / 2.0;
            float AE = 1.0/(sin(theta)*aspect);
            newPos.z += AE/length(AD) * (D.z - A.z);
            newPos.x += bisect.x*AE;
            newPos.y += bisect.y*AE;

            gl_Position = newPos;
        }

Dieser Code gibt uns die folgenden Ergebnisse.

Bildschirmfoto

Beachten Sie, dass es einige Randfälle gibt, die damit zu tun haben, dass durch diesen Prozess fast rückseitig ausgesuchte Dreiecke umgedreht werden, und ich begann, dies im Code zu behandeln, entschied mich jedoch, diese Fälle vorerst einfach zu vermeiden. Vielleicht werde ich es noch einmal besuchen, wenn ich dieses Projekt fertig habe.

Jackalope
quelle
1
Gute Arbeit, um das herauszufinden! Wirklich wie die mathematische Beschreibung am Anfang.
user1118321
0

Dies kann ohne trigonometrische Funktionen erreicht werden , indem die Verkleinerung incircle des Dreiecks.

incircle()berechnet den Kreis des durch die Eckpunkte gebildeten Dreiecks A,B,Cund gibt Mittelpunkt und Radius als zurück vec4. Die Eckpunkte X=A,B,Cwerden dann um den Bruchteil ihres Abstands zum Kreismittelpunkt ( Q-X) nach innen bewegt , der dem Verhältnis des gewünschten Randes zum Kreisradius ( m/Q.w) entspricht.

vec4 incircle(vec3 A, vec3 B, vec3 C) {
    float a = length(B - C), b = length(C - A), c = length(A - B);
    float abc = a + b + c;
    // http://mathworld.wolfram.com/Incenter.html
    vec3 I = (a * A + b * B + c * C) / abc;
    // http://mathworld.wolfram.com/Inradius.html
    float r = 0.5
            * sqrt((-a + b + c) * (a - b + c) * (a + b - c) / abc);
    return vec4(I, r);
}

vec3 A,B,C; // vertices
float m; // margin
vec4 Q = incircle(A,B,C);
A += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - A);
B += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - B);
C += clamp(m / Q.w, 0.0, 1.0) * (Q.xyz - C);
Adam
quelle
Sehr interessant, Adam! Ich hatte noch nichts von dieser Funktion gehört.
Jackalope