Как я могу надежно реализовать скины графических процессоров в Android?

11

Я пытаюсь заставить работать скины персонажей на Android.

Идея довольно ванильная: у меня есть свои матрицы скинов, и вместе с каждой вершиной я посылаю до четырех индексов матриц и четыре соответствующих веса. Я суммирую их в вершинном шейдере и применяю к каждой вершине.

Вот что я делаю в вершинном шейдере в iOS-версии моей игры (не берите в голову нормалей):

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;
}

И это работает довольно хорошо. Однако, с тем же кодом в Android, на некоторых устройствах (особенно Nexus 7 2013), вы не можете получить доступ к uniforms с непостоянными индексами. Другими словами, вы не можете сделать это:

bones[int(in_bone_index.w)]

потому что bones[some_non_constant]всегда оценивается как bones[0], что вовсе не забавно. Хуже всего то, что шейдерный компилятор с радостью компилирует это.

Казалось, у этого парня точно такая же проблема. Он решил это, получив доступ к униформе как векторам вместо матриц. Я сделал то же самое, и на самом деле это сработало!

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;
}

Но я думаю, что это сработало как шанс. uniformОни не предназначены для случайного доступа, поэтому я боюсь, что эта «техника» не будет работать на всех устройствах.

Этот парень передает свои матрицы в виде текстур, что довольно крутая идея. Я сделал текстуру OES_texture_float 4x32, где каждый тексель является строкой матрицы, а каждая строка текстуры - целой матрицей. Я получаю к нему доступ так:

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;
}

На самом деле, это работало довольно хорошо ... Пока я не попробовал это на моем Galaxy Note 2. На этот раз компилятор жаловался, что я не могу использовать texture2Dв вершинном шейдере!

Поэтому я проверяю, поддерживает ли GPU доступ к текстуре в вершинном шейдере и поддерживает ли он OES_texture_float. Если это так, я использую текстурный подход. Если это не так, я использую векторный подход.

Однако текстурный подход доступен не на всех платформах, а векторный подход работает случайно. Я хотел бы знать, есть ли способ передать мои матрицы скинов в вершинный шейдер, который надежно работает на всех устройствах.

У меня могут быть минимальные разумные требования к ОС, такие как Android 4.1+, но я хотел бы иметь решение, которое работает на всех устройствах, которые отвечают этим требованиям.

Панда Пижама
источник
Ну, я не могу придумать альтернатив, TBH. Я думаю, что вам лучше всего использовать обе методики, в зависимости от того, какая из них доступна, если ни одна из них не доступна, не используйте скины GPU, просто отступите к реализации скинов CPU (возможно, с менее детальными моделями). ?).
concept3d
@ concept3d: я не знаю, может быть, какое-то расширение, которое гарантированно существует на Android 4.1+, предназначенное для решения этой проблемы, или что-то концептуально иное с теми же результатами. Я считаю себя достаточно опытным в общих концепциях многих тем, но мои знания о платформе Android очень ограничены, и я подумал, что может быть чисто технический обход этой проблемы.
Панда Пижама
Поэтому, возможно, именно поэтому я не смог заставить работать скинг на моем Nexus 7.: / Спасибо, ваш вопрос открыл мне глаза!
async

Ответы:

4

Это несоответствующее поведение Nexus 7 (GPU Adreno). Вы говорите, что «униформа не предназначена для случайного доступа», но согласно Приложению A спецификации :

Униформа (исключая пробники)

В вершинном шейдере обязательна поддержка всех форм индексации массивов. В фрагментном шейдере поддержка индексации обязательна только для выражений с постоянными индексами.

Из обсуждения здесь следует, что эта ошибка относится только к матричным массивам, поэтому обходные пути с использованием векторов могут работать надежно и быть переносимыми на другие графические процессоры (я знаю, что случайная равномерная индексация работает, по крайней мере, на графических процессорах Mali и PowerVR).

GuyRT
источник
Хм, я думаю, это кажется правильным. Я думаю, что читал эту ветку раньше, но от Qualcomm нет подтверждения, подтверждающего эту проблему.
Панда Пижама