5
votes

I'm implementing skeletal animation in my android phone, and here is how I do it:

  1. Calcualte all bone transformation matrices on the CPU side

  2. Create a float texture to store these matrices (so this is done at the beginning of each frame). The codes look like below:

    if(texID) {
        glDeleteTextures(1, &texID);
        texID = 0;
    }
    
    glGenTextures(1, &texID);
    glBindTexture(GL_TEXTURE_2D, texID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, p);
    
  3. In vertex shader, fetch matrix from this texture and apply to vertex position

This approach works fine in my Motorola Milestone XT-701 using a POWERVR GPU. But when I run it on a Qualcomm snapdragon (SE Xperia X10i and Google Nexus one), there are many triangles disappeared (looks randomly), so it seems the model is flickering.

I also tried to reduce the scene complexity by rendering only one animated model, and the flicker become less but still exists. Does anyone know what I may be doing wrong? Is this some kind of synchronization problem?

You can see the snapshots here (the first two pictures is correct, and the later two are wrong). The APK file of my grogram can be downloaded here. (it does not require any permission so don't worry)

Here is the vertex shader I used:

struct light {
    lowp vec4    position;  // light position for a point/spot light or
                      // normalized dir. for a directional light
    lowp vec4    ambient_color;
    lowp vec4    diffuse_color;
    lowp vec4    specular_color;
    lowp vec3    spot_direction;
    lowp vec3    attenuation_factors;
    lowp float   spot_exponent;
    lowp float   spot_cutoff_angle;
    bool         compute_distance_attenuation;
};

struct material {
    lowp vec4    ambient_color;
    lowp vec4    diffuse_color;
    lowp vec4    specular_color;
    lowp vec4    emissive_color;
    lowp float   specular_exponent;
};

// uniforms used by the vertex shader
// uniform vec4 u_color;
uniform highp mat4     u_mvMatrix;
uniform highp mat4     u_projMatrix;
uniform bool           u_enable_lighting;
uniform light          u_light_state;
uniform material       u_material_state;
uniform bool           u_enable_texture;
uniform highp sampler2D   s_jointTex; // use highp for float texture

// attributes input to the vertex shader
// attribute lowp vec4 a_color;
attribute highp vec3    a_position;
attribute lowp vec3        a_normal;
attribute mediump vec2    a_texCoord;
attribute highp float    a_jointID;

// varying variables – input to the fragment shader
varying lowp vec4        v_front_color;
varying mediump vec2    v_texCoord;

vec2 mapTo2D(float idx)
{
    vec2 st = vec2(idx + 0.5, 0.5);
    return st / 256.0;
}

void main()
{
    mat4 joint = mat4(1.0);

    if(a_jointID >= 0.0)
    {
        float idx = a_jointID * 4.0;
        joint = mat4(    texture2D(s_jointTex, mapTo2D(idx)), 
                        texture2D(s_jointTex, mapTo2D(idx+1.0)), 
                        texture2D(s_jointTex, mapTo2D(idx+2.0)), 
                        texture2D(s_jointTex, mapTo2D(idx+3.0)) );
        gl_Position = (u_projMatrix * u_mvMatrix) * joint * vec4(a_position, 1.0); // hint compiler to extract uniform calculation
        // v_front_color = vec4(1.0, 0.0, 0.0, 1.0);
    }
    else
    {
        gl_Position = (u_projMatrix * u_mvMatrix) * vec4(a_position, 1.0); // hint compiler to extract uniform calculation
        // v_front_color = vec4(0.0, 1.0, 0.0, 1.0);
    }

    if(u_enable_lighting)
    {
        lowp vec4 computed_color = vec4(0.0);
        vec3 normal = normalize( vec3(u_mvMatrix * joint * vec4(a_normal, 0.0) ) );
        vec3 lightDir = normalize( vec3(u_mvMatrix * u_light_state.position) );
        float NdotL = max(dot(normal, lightDir), 0.0);

        computed_color += u_light_state.ambient_color * u_material_state.ambient_color + NdotL * u_light_state.diffuse_color * u_material_state.diffuse_color;

        if(NdotL > 0.0) {
            vec3 half_vec = normalize(lightDir + vec3(0.0, 0.0, 1.0)); // why?
            float NdotHV = dot(normal, half_vec);
            if(NdotHV > 0.0)
                computed_color += u_light_state.specular_color * u_material_state.specular_color * pow(NdotHV, u_material_state.specular_exponent);
        }
        v_front_color = computed_color;
    }
    else
        v_front_color = vec4(1.0, 1.0, 1.0, 1.0); // u_material_state.ambient_color; // TODO?

    v_texCoord = a_texCoord;
}
1
Why don't you use matrix uniforms to pass the skeletal matrices?datenwolf
The maximum number of matrix uniforms is restricted (at least it is in my device) so I don't think it is enough to hold all matrices for all models.Johnson
You shouldn't process all skeletal matrices at whole. Break down your model into smaller parts, which share only up to 4 bones. Keep in mind that you'll end up processing all matrices and then only create a superposition of only few of them, so passing the whole set is not very efficient as well. On regular OpenGL (not yet ES) there is the extension GL_ARB_uniform_buffer_object one could use instead of textures.datenwolf
Try using glTexSubImage2D instead of deleting and re-creating the image each frame ?Calvin1602
Thanks! I will try your suggestions later to see if it works.Johnson

1 Answers

5
votes
  1. Re-creating a texture each frame is not efficient. You can use the same texID and just call glTexImage* each frame.

  2. Why don't you use 1D texture? It would remove a burden of tex-coord conversion to 2D.

  3. Your joint matrix is 4x4, but you store it as 4 GL_RGBA vectors. This internal format does allow only [0,1] range, which is not appropriate for your task. Try using GL_RGBA_16f as internal format instead.