0
votes

I am doing GPU skinning in my vertex shader which works fine on PC, and which I'm porting to Android. My vertex shader is below, and the problem is that the creation of the matTransform matrix seems to only use the first matrix in boneMatrices:

#version 300 es

precision highp float;
precision highp int;

//Uniform count: projectionMatrix(16) + modelViewMatrix(16) + MVPMatrix(16) + textureMatrix(16) + normalMatrix(9) + lightMVPMatrices(16*5) + nShadowLights(1) + boneMatrices(16*boneMax)  = 73 + 1 + 16*shadowLightMax + 16*boneMax = (out of ~1024 components)
//GLSL ES (vectors): projectionMatrix(4) + modelViewMatrix(4) + MVPMatrix(4) + textureMatrix(4) + normalMatrix(3) + lightMVPMatrices(4*5) + nShadowLights(1) + boneMatrices(4*boneMax) = 19 + 4*shadowLightMax + 4*boneMax = 239 out of 256 vectors on Nexus 5 (shadowLightMax = 5, boneMax = 50, 17 vec4s remain, or 4 matrices and 1 vec4)
//Matrices
//uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 MVPMatrix;
uniform mat4 textureMatrix;
uniform mat3 normalMatrix;
uniform mat4 lightMVPMatrices[5];
uniform int nShadowLights;

//Bones
uniform mat4 boneMatrices[50];

//Vertex information
in vec3 position;
in vec4 colour;
in vec2 texCoord;
in vec3 normal;
in vec3 boneWeights;
in vec4 boneIndices;

out vec4 _colour;
out vec2 _texCoord;
out vec3 _normal;
out vec3 _eyePos;
out vec4 _lightPos[5];

void main(void)
{
    vec4 positionSkinned;
    vec4 normalSkinned;

    mat4 matTransform = boneMatrices[int(boneIndices[0])] * boneWeights[0];
    matTransform += boneMatrices[int(boneIndices[1])] * boneWeights[1];
    matTransform += boneMatrices[int(boneIndices[2])] * boneWeights[2];
    float finalWeight = 1.0 - (boneWeights[0] + boneWeights[1] + boneWeights[2]);
    matTransform += boneMatrices[int(boneIndices[3])] * finalWeight;

    positionSkinned = matTransform * vec4(position, 1.0);
    //positionSkinned.w = 1.0;
    normalSkinned = matTransform * vec4(normal, 0.0);

    gl_Position = MVPMatrix * positionSkinned;
    _colour = colour;
    _texCoord = (textureMatrix * vec4(texCoord, 0.0, 1.0)).xy;
    _normal = normalize(normalMatrix * normalize(normalSkinned.xyz));
    _eyePos = (modelViewMatrix * positionSkinned).xyz;
    for(int i = 0; i < nShadowLights; i++)
        _lightPos[i] = lightMVPMatrices[i] * positionSkinned;
}

I have verified that:

1) the correct matrices get pushed into boneMatrices
2) the correct bone indexes exist within boneIndices
3) the correct boneWeights exist within boneWeights
4) accessing components of boneIndices with dot notation (.x, .y, .z and .w) doesn't make a different
5) There are no OpenGL errors at all, as I check for errors after every call, and uniform size isn't an issue (if I increase boneMatrices by 5 extra matrices, I get invalid operation errors after each time I push matrices to the shader, but at this size and lower it's fine)

I have checked points 1, 2 and 3 (boneMatrices, boneIndices and boneWeights are correct) by doing the following:

1) using a specific animation which modified a few bones only (e.g. boneMatrix[6]), then hard-coding boneMatrix[6] and verifying that all vertices get properly modified by this single matrix, with the same result on PC and Android

2) drawing out boneIndices by doing the following in the vertex shader:

_colour = vec4(boneIndices[0], boneIndices[1], boneIndices[2], boneIndices[3]);

and the following in the fragment shader:

gl_FragColor = _colour

with the same colours on PC and Android

3) doing the same as above but with setting _colour to:

_colour = vec4(boneWeights[0], boneWeights[1], boneWeights[2], finalWeight);

I have no idea what else to try, and it definitely seems to be that only the first matrix is used and that for some reason, int(boneIndices[x]) results in 0 for any x. This is on a Nexus 5 with an OpenGL ES 3.0. Help!

EDIT: Taking Andon's advice of using ivec4's instead of vec4's for boneIndices unfortunately results in the same result, however at least this clears up that it isn't a casting issue from float. Now I feel like a cop without any leads :/

1
Any reason in particular you are using a floating-point vector to store your indices? ivec4 would make a lot more sense. You can supply data to an integer vertex attribute using glVertexAttribIPointer (...). You may also be coming close to running out of uniform locations in this GLSL program, an array of 50 mat4s by itself uses 200 uniform locations and GL ES 3.0 is only required to give you 256 total. If this were a fragment shader, you would already be over the limit of 224. - Andon M. Coleman
I can't remember off the top of my head, but I do remember there being some sort of reason when I did it a while ago (something I read I think). I'll try changing it to use integers and see if that works. Also yep, done the uniform space calculations for GL ES at the top of the shader in the comments :) - Rajveer
I've made those changes and get the same result unfortunately. I've also verified that the changes are correct by again outputting the ivec4 boneIndices as colour _colour = vec4(float(boneIndices.x), float(boneIndices.y), float(boneIndices.z), float(boneIndices.w));. Here is an image of it on Windows showing all bone matrices being used, and here is a screenshot from Android showing only the first being used (with bone indices used for colour). - Rajveer
If you could please share how you are sending the 50-matrix array to the vertex shader, it would be very nice. I'm struggling with that part. - async
I send them as a array of vec4's instead of matrices as that's how the Adreno SDK does it: glUniform4fv(boneUniformLocation, bones.size()*16, (GLfloat*) &bones[0].values[0]); where bones is an array of matrices with 16 GLfloats. - Rajveer

1 Answers

1
votes

In the end this looks to be a limitation of the Adreno driver, which doesn't support indexing a uniform array of matrices without a constant integer contrary to what is mandatory within the OpenGL ES spec. A workaround however is to just use a uniform array of vec4s, as it does seem to support indexing these with variables (as is done within their SDK).