Я намагаюся змусити скин-героїв працювати на 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
s призначені не для випадкового доступу, тому я побоююся, що ця "техніка" працюватиме не на кожному пристрої.
Цей хлопець передає свої матриці як текстури, що є досить крутою ідеєю. Я зробив текстуру 4x32 OES_texture_float, де кожен тексель є матричним рядком, а кожен текстурний ряд - цілою матрицею. Я отримую доступ до нього так:
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+, але я хотів би мати рішення, яке працює на всіх пристроях, які відповідають цим вимогам.