Я пытаюсь заставить работать скины персонажей на 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), вы не можете получить доступ к uniform
s с непостоянными индексами. Другими словами, вы не можете сделать это:
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+, но я хотел бы иметь решение, которое работает на всех устройствах, которые отвечают этим требованиям.