Android에서 GPU 스키닝을 안정적으로 구현하려면 어떻게해야합니까?

Android에서 캐릭터 스키닝을 사용하려고합니다.

아이디어는 상당히 바닐라입니다. 저는 스키닝 매트릭스를 사용하고 각 정점과 함께 최대 4 개의 매트릭스 인덱스와 4 개의 해당 가중치를 보냅니다. 정점 셰이더에 합산하여 각 정점에 적용합니다.

이것이 내 게임의 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상수가 아닌 인덱스로에 액세스 할 수 없습니다 . 다른 말로하면, 당신은 이것을 할 수 없습니다 :

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

그러나 나는 이것이 우연히 일했다고 생각합니다. uniforms는 무작위로 액세스 할 수있는 것은 아니므로이 “기술”이 모든 장치에서 작동하지는 않을 것입니다.

이 사람 은 그의 행렬을 질감으로 전달하고 있습니다. 4×32 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 이상과 같은 최소한의 합리적인 OS 요구 사항을 가질 수 있지만 해당 요구 사항을 충족하는 모든 장치에서 작동하는 솔루션을 원합니다.



답변

이는 Nexus 7 (Adreno GPU)의 부적합한 행동입니다. “유니폼은 무작위로 접근하기위한 것이 아니라”라고 말하지만, 스펙의 부록 A에 따르면 :

유니폼 (샘플러 제외)

꼭짓점 셰이더에서는 모든 형태의 배열 인덱싱을 지원해야합니다. 프래그먼트 셰이더에서 인덱싱에 대한 지원은 상수 인덱스 식에만 적용됩니다.

여기서 논의한 바에 따르면이 버그는 균일 한 매트릭스 배열에만 적용되므로 벡터를 사용하는 해결 방법은 안정적으로 작동하고 다른 GPU로 이식 할 수 있습니다 (최소한 Mali 및 PowerVR GPU에서 임의의 균일 한 색인 생성이 작동 함을 알고 있습니다).