Wie kann ich GPU-Skinning in Android zuverlässig implementieren?


10

Ich versuche, das Skinning von Charakteren auf Android zum Laufen zu bringen.

Die Idee ist ziemlich vanille: Ich habe meine Skinning-Matrizen und sende zusammen mit jedem Scheitelpunkt bis zu vier Matrixindizes und vier entsprechende Gewichte. Ich summiere sie im Vertex-Shader und wende sie auf jeden Vertex an.

Dies ist, was ich im Vertex-Shader in der iOS-Version meines Spiels mache (beachte die Normalen nicht):

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection;
uniform mat4 bones[@bind_matrix_count];

void main()
{
    // Skinning
    vec4 transformed_pos =
        ((in_pos * bones[int(in_bone_index.x)]) * in_bone_weight.x) +
        ((in_pos * bones[int(in_bone_index.y)]) * in_bone_weight.y) +
        ((in_pos * bones[int(in_bone_index.z)]) * in_bone_weight.z) +
        ((in_pos * bones[int(in_bone_index.w)]) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

Und es funktioniert ziemlich gut. Mit demselben Code in Android können Sie jedoch auf einigen Geräten (insbesondere dem Nexus 7 2013) nicht auf uniforms mit nicht konstanten Indizes zugreifen . Mit anderen Worten, Sie können dies nicht tun:

bones[int(in_bone_index.w)]

denn bones[some_non_constant]wird immer als bewertet bones[0], was überhaupt nicht amüsant ist. Das Schlimmste ist, dass der Shader-Compiler dies gerne kompiliert.

Dieser Typ schien genau das gleiche Problem zu haben. Er löste es, indem er auf die Uniformen als Vektoren anstelle von Matrizen zugegriffen hatte. Ich habe das gleiche getan, und tatsächlich hat es funktioniert!

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection;
uniform vec4 bones[@bind_matrix_count * 4]; // four vec4's for each matrix

void main()
{
    // Skinning
    mat4 skin_0 = mat4(
        bones[4 * int(in_bone_index.x) + 0],
        bones[4 * int(in_bone_index.x) + 1],
        bones[4 * int(in_bone_index.x) + 2],
        bones[4 * int(in_bone_index.x) + 3]);
    mat4 skin_1 = mat4(
        bones[4 * int(in_bone_index.y) + 0],
        bones[4 * int(in_bone_index.y) + 1],
        bones[4 * int(in_bone_index.y) + 2],
        bones[4 * int(in_bone_index.y) + 3]);
    mat4 skin_2 = mat4(
        bones[4 * int(in_bone_index.z) + 0],
        bones[4 * int(in_bone_index.z) + 1],
        bones[4 * int(in_bone_index.z) + 2],
        bones[4 * int(in_bone_index.z) + 3]);
    mat4 skin_3 = mat4(
        bones[4 * int(in_bone_index.w) + 0],
        bones[4 * int(in_bone_index.w) + 1],
        bones[4 * int(in_bone_index.w) + 2],
        bones[4 * int(in_bone_index.w) + 3]);
    vec4 transformed_pos =
        ((in_pos * skin_0) * in_bone_weight.x) +
        ((in_pos * skin_1) * in_bone_weight.y) +
        ((in_pos * skin_2) * in_bone_weight.z) +
        ((in_pos * skin_3) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

Aber ich denke, das hat als Zufall funktioniert. uniforms sind nicht für den zufälligen Zugriff gedacht, daher befürchte ich, dass diese "Technik" nicht auf jedem Gerät funktioniert.

Dieser Typ gibt seine Matrizen als Texturen weiter, was eine ziemlich coole Idee ist. Ich habe eine 4x32 OES_texture_float-Textur erstellt, wobei jedes Texel eine Matrixzeile und jede Texturzeile eine gesamte Matrix ist. Ich greife folgendermaßen darauf zu:

attribute vec4 in_pos;
attribute vec4 in_normal;
attribute vec2 in_texture_coords;
attribute vec4 in_bone_index;
attribute vec4 in_bone_weight;

varying vec2 fs_texture_coords;

uniform mat4 world_view_projection; // A texture!
uniform sampler2D bones;

void main()
{
    // Skinning
    mat4 bone0 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.x / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.x / 32.0)));
    mat4 bone1 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.y / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.y / 32.0)));
    mat4 bone2 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.z / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.z / 32.0)));
    mat4 bone3 = mat4(
        texture2D(bones, vec2(0.00, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.25, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.50, in_bone_index.w / 32.0)),
        texture2D(bones, vec2(0.75, in_bone_index.w / 32.0)));
    vec4 transformed_pos =
        ((in_pos * bone0) * in_bone_weight.x) +
        ((in_pos * bone1) * in_bone_weight.y) +
        ((in_pos * bone2) * in_bone_weight.z) +
        ((in_pos * bone3) * in_bone_weight.w);

    gl_Position = world_view_projection * transformed_pos;
    fs_texture_coords = in_texture_coords;
}

Tatsächlich hat das ganz gut funktioniert ... Bis ich es auf meinem Galaxy Note 2 ausprobiert habe. Diesmal hat sich der Compiler beschwert, dass ich es nicht texture2Dauf dem Vertex-Shader verwenden kann!

Ich überprüfe also, ob die GPU Texturzugriffe auf den Vertex-Shader unterstützt und ob sie OES_texture_float unterstützt. Wenn ja, verwende ich den Texturansatz. Wenn nicht, verwende ich den Vektoransatz.

Der Texturansatz ist jedoch nicht auf allen Plattformen verfügbar, und der Vektoransatz funktioniert zufällig. Ich würde gerne wissen, ob es eine Möglichkeit gibt, meine Skinning-Matrizen an den Vertex-Shader zu übergeben, der auf allen Geräten zuverlässig funktioniert.

Ich kann angemessene Mindestanforderungen an das Betriebssystem haben, wie z. B. Android 4.1+, aber ich möchte eine Lösung, die auf allen Geräten funktioniert, die diese Anforderungen erfüllen.


Nun, ich kann mir keine Alternativen vorstellen. TBH Ich denke, Ihre beste Wahl ist es, beide Techniken zu verwenden, je nachdem, welche verfügbar sind. Wenn keine verfügbar sind, verwenden Sie kein GPU-Skinning, sondern greifen Sie einfach auf die CPU-Skinning-Implementierung zurück (wahrscheinlich mit weniger detaillierten Modellen ?).
Konzept3d

@ concept3d: Ich weiß nicht, vielleicht gibt es eine Erweiterung, die auf Android 4.1+ garantiert ist, um dieses Problem zu lösen, oder etwas konzeptionell anderes mit den gleichen Ergebnissen. Ich halte mich für ziemlich kompetent in den allgemeinen Konzepten vieler Themen, aber meine Kenntnisse der Android-Plattform sind sehr begrenzt, und ich dachte, dass es möglicherweise eine rein technische Problemumgehung für dieses Problem gibt.
Panda Pyjama

Vielleicht konnte ich deshalb nicht häuten, um an meinem Nexus 7 zu arbeiten: / Danke, deine Frage hat mir die Augen geöffnet!
Async

Antworten:


4

Dies ist ein nicht konformes Verhalten des Nexus 7 (Adreno GPU). Sie sagen, "Uniformen sollen nicht zufällig abgerufen werden", aber gemäß Anhang A der Spezifikation :

Uniformen (ohne Probenehmer)

Im Vertex-Shader ist die Unterstützung aller Formen der Array-Indizierung vorgeschrieben. Im Fragment-Shader ist die Unterstützung der Indizierung nur für Konstant-Index-Ausdrücke vorgeschrieben.

Aus der Diskussion hier geht hervor, dass dieser Fehler nur für einheitliche Matrix-Arrays gilt, sodass die Umgehung von Vektoren wahrscheinlich zuverlässig funktioniert und auf andere GPUs portierbar ist (ich weiß, dass die zufällige einheitliche Indizierung zumindest auf Mali- und PowerVR-GPUs funktioniert).


Hmm, ich denke das scheint richtig zu sein. Ich glaube, ich hatte diesen Thread schon einmal gelesen, aber es gibt keine Bestätigung von Qualcomm, die dieses Problem bestätigt.
Panda Pyjama
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.