2017-12-29 40 views
8

Ich versuche, etwas Arbeit mit der GPU für eine Tuch-Simulation zu tun, und ich habe einige Probleme mit unterschiedlicher Hardware. Ich benutze Threejs als Rahmen, aber ich glaube, das ist nicht relevant für das Problem, das ich habe.GLSL Matrix/Umkehr Multiplikation Präzision

Im Grunde lade ich eine Matrix und eine Umkehrung dieser Matrix hoch, um Punkte von lokalen Koordinaten in Welt zu transformieren, mache einige Berechnungen in Weltkoordinaten (wie Kollisionserkennung) und transformiere sie dann zurück in Lokal. Dies funktioniert gut auf meinem Laptop, wenn ich mit Punkt Texturen schwimmen, aber bemerkte ich auf meinem Handy gibt es einige bizarre Artefakte:

richtig: img

falsch: img

Nachdem einige Debug tun Ich habe verengte es auf zwei Probleme. Beide beziehen sich auf die Dezimalgenauigkeit. Das Kollabieren der Vertices aufgrund von Constraints (und Präzisionsproblemen während der Constraints) und das Verlieren der Genauigkeit bei Verwendung der Matrixmultiplikation und der Inversen.

Der Grund, warum ich glaube, dass das Problem mit der Genauigkeit zusammenhängt, ist, weil wenn ich eine Fließkomma-Textur verwende, funktioniert es auf meinem Computer, aber wenn ich einen halben Schwimmer verwende, habe ich die gleichen Probleme. Mein Telefon unterstützt Floating-Point-Texturen. Das ist einer der Gründe, warum ich verwirrt bin, warum das auf meinem Handy passiert. Ich habe das Problem eingegrenzt, so dass die gesamte Stoffsimulation deaktiviert ist und wenn ich die Anwendung mit halben Float-Texturen auf meinem Computer ohne Gravitation, aber mit der Transformation und umgekehrt die flare-Art von Flackern auf seltsame Weise flackert, während if Die Transformation und Inverse sind deaktiviert, dann sieht es normal aus.

Ich habe keine Ideen, wie ich mit diesem Problem umgehen soll oder ob ich sogar den richtigen Weg eingeschlagen habe. Ich glaube, dass Halb-Float-Texturen eine begrenzte Dezimal-Genauigkeit haben, aber ich verstehe nicht, warum dies meine Probleme verursachen würde, da es nur die Ausgabe des Shaders beeinflussen sollte, nicht die Mathe, die im Shader läuft.

Der Code für den Shader sieht wie folgt aus:

' vec2 cellSize = 1.0/res;', 
    ' vec4 pos = texture2D(vertexPositions, vuv.xy);', 


    ' vec2 newUV;', 
    ' if(type == 0.0){', 
     ' float px = floor(vuv.x * res.x);', 
     ' float spacingx = px- (2.0 * floor(px/2.0));', 
     ' float py = floor(vuv.y * res.y);', 
     ' float spacingy = py- (2.0 * floor(py/2.0));', 
     ' float total = spacingx + spacingy;', 
     ' total = total- (2.0 * floor(total/2.0));', 

     ' if(total == 0.0){', 
     '  newUV = vuv + (direction * cellSize);', 
     ' }', 
     ' else{', 
     '  newUV = vuv - (direction * cellSize);', 
     ' }', 
    ' }', 
    ' if(type == 1.0){', 
     ' float px = floor(vuv.x * res.x);', 
     ' float spacingx = px- (2.0 * floor(px/2.0));', 

     ' float total = spacingx;', 


     ' if(total == 0.0){', 
     '  newUV = vuv + (direction * cellSize);', 
     ' }', 
     ' else{', 
     '  newUV = vuv - (direction * cellSize);', 
     ' }', 
    ' }', 






    ' vec4 totalDisplacement = vec4(0.0);', 

    '   if(newUV.x > 0.0 && newUV.x < 1.0 && newUV.y > 0.0 && newUV.y < 1.0){ ', 
    '    vec4 posOld = texture2D(vertexPositionsStart, vuv);' , 
    '    vec4 posOld2 = texture2D(vertexPositionsStart, newUV);' , 

    '    float targetDistance = length(posOld - posOld2);', 
    '    vec4 newPos = texture2D(vertexPositions, newUV);', 
    '    float dx = pos.x - newPos.x;', 
    '    float dy = pos.y - newPos.y;', 
    '    float dz = pos.z - newPos.z;', 
    '    float distance = sqrt(dx * dx + dy * dy + dz * dz);', 
    '    float difference = targetDistance- distance;', 
    '    float percent = difference/distance/2.0;', 
    '    float offsetX = dx * percent * rigid;', 
    '    float offsetY = dy * percent * rigid;', 
    '    float offsetZ = dz * percent * rigid;', 
    '    totalDisplacement.x += offsetX;', 
    '    totalDisplacement.y += offsetY;', 
    '    totalDisplacement.z += offsetZ;', 
    '   }', 
    '  }', 
    ' }', 

    ' pos += totalDisplacement;', 
    ' if( vuv.x > 1.0 - cellSize.x && topConstrain == 1){', 
    '  pos =transformation * texture2D(vertexPositionsStart, vuv.xy);', 
    ' }', 

    ' if( vuv.x < cellSize.x && bottomConstrain == 1){', 
    '  pos =transformation * texture2D(vertexPositionsStart, vuv.xy);', 
    ' }', 

    ' if( vuv.y < cellSize.y && leftConstrain == 1){', 
    '  pos =transformation * texture2D(vertexPositionsStart, vuv.xy);', 
    ' }', 


    ' if( vuv.y > 1.0 - cellSize.y && rightConstrain == 1){', 
    '  pos =transformation * texture2D(vertexPositionsStart, vuv.xy);', 
    ' }', 




    ' gl_FragColor = vec4(pos.xyz , 1.0);', 
+1

Die Genauigkeitsanforderungen für GLES sind viel niedriger als für Desktop-GL (besonders wenn Sie mit GLES2 arbeiten). Es hilft nicht, wenn Sie volle fp32-Texturen verwenden, wenn Ihre Shader-ALUs immer noch eine viel geringere Präzision verwenden. – derhass

+0

Ich sehe. Sie denken also, das Problem ist, dass die Shader-ALUs meines Telefons nicht genug Präzision unterstützen. Ich verstehe nicht, warum dieses Problem immer noch auftritt, wenn ich auf meinem Computer Halb-Float-Texturen verwende. Das scheint jedoch eine vernünftige Erklärung zu sein, obwohl –

+1

versuchen, das ** relative Koordinatensystem ** zu verwenden, damit die transformierten Eckpunkte nicht zu weit von Ihren [Matrizenursprüngen] entfernt sind (https://stackoverflow.com/questions/28075743/how-do- i-compose-a-rotation-matrix-mit-menschenlesbaren-winkel-von-scratch/28084380? s = 1 | 64.0697 # 28084380).Nach der Berechnung zurück in das ursprüngliche Koordinatensystem übersetzen. Auf diese Weise vermeiden Sie, Vektoren mit hoher Magnitude mit Matrizen zu multiplizieren, was zu Genauigkeitsproblemen führt. Weitere Informationen finden Sie unter [Verbesserung der Genauigkeit der Ray- und Ellipsoid-Schnittpunkte] (https://stackoverflow.com/q/25470493/2521214) – Spektre

Antwort

1

Um sicherzustellen, dass Ihre Shader hohe Präzision Variablen für Fließkommaberechnungen, sollte folgendes hinzugefügt werden, um den Anfang der Vertex-Shader erstellt:

precision highp float; 
precision highp int; 

Und im Fragment-Shader sollte Gleitkomma Variablendeklarationen deklariert werden wie folgt:

precision highp float; 

Gleitkommafehler werden vergrößert, wenn Sie Werte in Ihren Berechnungen verwenden, die das Ergebnis von vorherigen Berechnungen sind, die als Gleitkommazahlen gespeichert sind. Ansonsten bekannt als Zwischenwerte.

Um diese Fehler zu minimieren, sollten Sie die Anzahl der im Shader ausgeführten Zwischenberechnungen begrenzen.Zum Beispiel können Sie in vollem Umfang die Berechnungen für newUV erweitern:

newUV = vuv + (direction * (1.0/res)); 

Sie können auch vollständig die Berechnung für totalDisplacement, Schritt für Schritt erweitern, erste Ersatz offsetX wie so:

totalDisplacement.x += (dx * percent * rigid) 

Jetzt jede Variable ersetzen dx und percent in die obige:

totalDisplacement.x += ((pos.x - newPos.x) * (difference/distance/2.0) * rigid) 

Y ou kann nun sehen, dass die Gleichung noch weiter ausgebaut werden kann, ersetzen Sie in difference wie so:

totalDisplacement.x += ((pos.x - newPos.x) * ((targetDistance- distance)/(distance * 2.0)) * rigid); 

An dieser Stelle Sie etwas Algebra tun können einige Variablen zu vereinfachen und aufheben (Division durch distance). Durch die obige Gleichung vereinfacht erhalten wir nun folgendes:

totalDisplacement.x += ((pos.x - newPos.x) * ((targetDistance/(distance * 2.0) - 0.5) * rigid); 

Endlich können wir die Formel für die targetDistance wie so ersetzen:

totalDisplacement.x += ((pos.x - newPos.x) * ((length(posOld - posOld2)/(distance * 2.0) - 0.5) * rigid); 

und jeweils für die anderen Koordinaten:

totalDisplacement.y += ((pos.y - newPos.y) * ((length(posOld - posOld2)/(distance * 2.0) - 0.5) * rigid); 
totalDisplacement.z += ((pos.z - newPos.z) * ((length(posOld - posOld2)/(distance * 2.0) - 0.5) * rigid); 

Und natürlich können Sie weitermachen, dh indem Sie den Wert für posOld, posOld2 und newPos ersetzen.

Beachten Sie, dass nun bis zu diesem Punkt, um die Gleichung zu erhalten oben haben wir die Notwendigkeit der Zwischenwerte in float Variablen zu speichern eliminiert. Auch durch die Vereinfachung der Gleichung (Division durch distance) wird die distance Variable nur einmal in der Berechnung verwendet. Vergleichen Sie dies mit der ursprünglichen Implementierung, distance wird verwendet, um difference und percent zu berechnen. Wenn Sie alles in einer Gleichung kombinieren, können Sie die Häufigkeit, mit der derselbe Gleitkommawert verwendet wird, vereinfachen und reduzieren. Daher reduziert sich auch der gesamte Gleitkommafehler. Der Kompromiss hier ist, dass die resultierende Gleichung weniger menschlich lesbar ist.

int range[2], precision; 
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, GL_HIGH_FLOAT, range, &precision); 

Wenn Sie das Ergebnis dieser für die desktop und mobile Version Ihrer Anwendung prüfen,:

Wenn Sie neugierig sind, können Sie auch das Maß an Präzision für einen bestimmten Shader-Compiler durch den Aufruf glGetShaderPrecisionFormat inspizieren Sie können den Unterschied in der Genauigkeit für die beiden Versionen vergleichen.