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


11

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는 무작위로 액세스 할 수있는 것은 아니므로이 "기술"이 모든 장치에서 작동하지는 않을 것입니다.

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


글쎄, 대안을 생각할 수 없다 .TBH 가장 좋은 방법은 사용 가능한 기술에 따라 두 가지 기술을 모두 사용하는 것입니다. ?).
concept3d

@ concept3d : 모르겠습니다. 아마도 4.1 이상 에서이 문제를 해결하도록 설계되었거나 동일한 결과로 개념적으로 다른 것을 수행 할 수있는 확장 기능을 모릅니다. 나는 많은 주제의 일반적인 개념에 능숙하다고 생각하지만 Android 플랫폼에 대한 지식은 매우 제한적 이며이 문제에 대한 순수한 기술적 해결 방법이있을 것이라고 생각했습니다.
Panda Pajama

어쩌면 이것이 Nexus 7에서 스킨 작업을 할 수 없었던 이유 일 것입니다.
async

답변:


4

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

유니폼 (샘플러 제외)

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

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


흠, 그게 옳은 것 같아요. 이전에 해당 스레드를 읽은 것으로 생각되지만 Qualcomm에서이 문제를 확인하는 승인은 없습니다.
Panda Pajama 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.