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 uniform
s 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. uniform
s 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 texture2D
auf 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.