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;
}
그러나 나는 이것이 우연히 일했다고 생각합니다. uniform
s는 무작위로 액세스 할 수있는 것은 아니므로이 “기술”이 모든 장치에서 작동하지는 않을 것입니다.
이 사람 은 그의 행렬을 질감으로 전달하고 있습니다. 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에서 임의의 균일 한 색인 생성이 작동 함을 알고 있습니다).