6
\$\begingroup\$

I am implementing an OpenGL ES 2.0 renderer in c. I want to use quaternions for rotations. Please take a look at the way I am implementing the rotation math. Everything looks as expected when the program runs, but since I am very new to using quaternions, I am wondering if I am doing things in a sub-optimal way. The idea is that the program logic will determine the current angle of rotation for a 3d object and inform the renderer. The renderer will then find the sin and cos of that angle, form a quaternion based on a unit vector representing the rotation axis (fixed for now), and then create a rotation matrix from the quaternion. This whole process would take place for each object on each frame. I have no idea if this approach is wildly inefficient.

Also, I have been reading that I should combine all matrix transformations before uploading them to a vertex shader in order to avoid calculating the combined transformation repeatedly for each vertex. Therefore I made a function that specifically multiplies 4x4 matrices, and I do all the matrix multiplication in the c code, and I'm wondering if there is some way to use the graphics hardware for these calculations.

I am posting the renderer. For running this in iOS, I am using glkit, and on android, I am using the GLsurfaceView. These wrappers are only used to handle setting up the gl context and the displaying and switching of frame buffers. (I can post the xcode and android studio projects if anyone wants to run the code.)

#define pi 3.14159265358979323846264338327
#define PI_OVER_360 0.0087266

GLuint _positionSlot;
GLuint _colorSlot;
GLuint _modelUniform;

GLfloat screenWidthInPixels;
GLfloat screenHeightInPixels;

float timeDiff = 0;
float lastTimeStamp = 0;

float modelMatrix[16] = {0};
float perspectiveMatrix[16] = {0};
float translationMatrix[16] = {0};
float rotationMatrix[16] = {0};
float transRotMatrix[16] = {0}; //To store the result of the translation matrix multiplied by the rotation matrix
float angle = 0;
float rotAxis[3] = {1,.1,.5};
float quat[4] = {0}; //rotation quaternion
float xx,yy,zz,xy,xz,yz,wx,wy,wz;

typedef struct
{
    float Position[3];
    float Color[4];
} Vertex;

const Vertex Vertices[] = {
    {{1, -1, 1}, {1, 0, 0, 1}},
    {{1, 1, 1}, {0, 1, 0, 1}},
    {{-1, 1, 1}, {0, 0, 1, 1}},
    {{-1, -1, 1}, {1, 1, 0, 1}},
    {{1, -1, -1}, {1, 0, 1, 1}},
    {{1, 1, -1}, {1, 1, 1, 1}},
    {{-1, 1, -1}, {0, 1, 1, 1}},
    {{-1, -1, -1}, {0, 0, 0, 1}}
};

const GLubyte Indices[] = {
    0, 1, 2,
    2, 3, 0,
    4, 6, 5,
    4, 7, 6,
    2, 7, 3,
    7, 6, 2,
    0, 4, 1,
    4, 1, 5,
    6, 2, 1,
    1, 6, 5,
    0, 3, 7,
    0, 7, 4
};

void buildPerspProjMat(float* m, float fov, float aspect, float znear, float zfar)
{
    float xymax = znear * tan(fov * PI_OVER_360);
    float ymin = -xymax;
    float xmin = -xymax;
    float width = xymax - xmin;
    float height = xymax - ymin;
    float depth = zfar - znear;
    float q = -(zfar + znear) / depth;
    float qn = -2 * (zfar * znear) / depth;
    float w = 2 * znear / width;
    w = w / aspect;
    float h = 2 * znear / height;
    m[0]  = w; m[1]  = 0; m[2]  = 0;  m[3]  =  0;
    m[4]  = 0; m[5]  = h; m[6]  = 0;  m[7]  =  0;
    m[8]  = 0; m[9]  = 0; m[10] = q;  m[11] = -1;
    m[12] = 0; m[13] = 0; m[14] = qn; m[15] =  0;
}
void setTranslationMatrix(float* m, float tx, float ty, float tz)
{
    m[0]  = 1; m[4]  = 0; m[8]  = 0;  m[12] = tx;
    m[1]  = 0; m[5]  = 1; m[9]  = 0;  m[13] = ty;
    m[2]  = 0; m[6]  = 0; m[10] = 1;  m[14] = tz;
    m[3]  = 0; m[7]  = 0; m[11] = 0;  m[15] =  1;
}
void setRotationMatrx(float* m, float* q)
{
    xx = q[0] * q[0];
    yy = q[1] * q[1];
    zz = q[2] * q[2];
    xy = q[0] * q[1];
    xz = q[0] * q[2];
    yz = q[1] * q[2];
    wx = q[3] * q[0];
    wy = q[3] * q[1];
    wz = q[3] * q[2];

    m[0]  = 1 - 2 * (yy + zz); m[4] =     2 * (xy - wz); m[8]  =     2 * (xz + wy);  m[12] = 0;
    m[1]  =     2 * (xy + wz); m[5] = 1 - 2 * (xx + zz); m[9]  =     2 * (yz - wx);  m[13] = 0;
    m[2]  =     2 * (xz - wy); m[6] =     2 * (yz + wx); m[10] = 1 - 2 * (xx + yy);  m[14] = 0;
    m[3]  =                 0; m[7] =                 0; m[11] = 0                ;  m[15] = 1;
}
void matMult4x4by4x4(float* m, float* a, float* b)
{
    m[0]  = a[0]*b[0]  + a[4]*b[1]  + a[8]* b[2]  + a[12]*b[3];
    m[1]  = a[1]*b[0]  + a[5]*b[1]  + a[9]* b[2]  + a[13]*b[3];
    m[2]  = a[2]*b[0]  + a[6]*b[1]  + a[10]*b[2]  + a[14]*b[3];
    m[3]  = a[3]*b[0]  + a[7]*b[1]  + a[11]*b[2]  + a[15]*b[3];
    m[4]  = a[0]*b[4]  + a[4]*b[5]  + a[8]* b[6]  + a[12]*b[7];
    m[5]  = a[1]*b[4]  + a[5]*b[5]  + a[9]* b[6]  + a[13]*b[7];
    m[6]  = a[2]*b[4]  + a[6]*b[5]  + a[10]*b[6]  + a[14]*b[7];
    m[7]  = a[3]*b[4]  + a[7]*b[5]  + a[11]*b[6]  + a[15]*b[7];
    m[8]  = a[0]*b[8]  + a[4]*b[9]  + a[8]* b[10] + a[12]*b[11];
    m[9]  = a[1]*b[8]  + a[5]*b[9]  + a[9]* b[10] + a[13]*b[11];
    m[10] = a[2]*b[8]  + a[6]*b[9]  + a[10]*b[10] + a[14]*b[11];
    m[11] = a[3]*b[8]  + a[7]*b[9]  + a[11]*b[10] + a[15]*b[11];
    m[12] = a[0]*b[12] + a[4]*b[13] + a[8]* b[14] + a[12]*b[15];
    m[13] = a[1]*b[12] + a[5]*b[13] + a[9]* b[14] + a[13]*b[15];
    m[14] = a[2]*b[12] + a[6]*b[13] + a[10]*b[14] + a[14]*b[15];
    m[15] = a[3]*b[12] + a[7]*b[13] + a[11]*b[14] + a[15]*b[15];
}

GLuint compileShader(const GLchar* shaderSourceString, GLenum shaderType)
{
    GLuint shaderHandle = glCreateShader(shaderType);
    glShaderSource(shaderHandle, 1, &shaderSourceString, NULL);
    glCompileShader(shaderHandle);
    GLint compileSuccess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
    if (compileSuccess == GL_FALSE)
    {
        GLchar messages[256];
        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
        //Todo - print messages
        exit(1);
    }
    return shaderHandle;
}
void compileShaders()
{
    GLchar vertexShaderString[] =
    "attribute vec4 Position; \n"
    "attribute vec4 SourceColor; \n"
    "varying vec4 DestinationColor; \n"
    "uniform mat4 Model; \n"
    "void main(void) \n"
    "{ \n"
    "    DestinationColor = SourceColor; \n"
    "    gl_Position = Model * Position; \n"
    "} \0";

    GLchar fragmentShaderString[] =
    "varying lowp vec4 DestinationColor; \n"
    "void main(void) \n"
    "{ \n"
    "    gl_FragColor = DestinationColor; \n"
    "} \0";

    GLuint compiledVertexShaderReference = compileShader(vertexShaderString, GL_VERTEX_SHADER);
    GLuint compiledFragmentShaderReference = compileShader(fragmentShaderString, GL_FRAGMENT_SHADER);
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, compiledVertexShaderReference);
    glAttachShader(programHandle, compiledFragmentShaderReference);
    glLinkProgram(programHandle);
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE)
    {
        GLchar messages[256];
        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
        //Todo - print messages
        exit(1);
    }
    glUseProgram(programHandle);
    _positionSlot = glGetAttribLocation(programHandle, "Position");
    _colorSlot = glGetAttribLocation(programHandle, "SourceColor");
    _modelUniform = glGetUniformLocation(programHandle, "Model");
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_colorSlot);
}

void setupVBOs()
{
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);

    GLuint indexBuffer;
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}

void initView(float screenWidthInPixelsPar, float screenHeightInPixelsPar)
{
    compileShaders();
    setupVBOs();

    screenWidthInPixels = screenWidthInPixelsPar;
    screenHeightInPixels = screenHeightInPixelsPar;

    float fov=30.0f; // in degrees
    float aspect=((float)screenWidthInPixels)/screenHeightInPixels;
    float znear=10.0f;
    float zfar=100.0f;
    buildPerspProjMat(perspectiveMatrix, fov, aspect, znear, zfar);
    glViewport(0, 0, screenWidthInPixels, screenHeightInPixels);

    glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
    glEnable(GL_DEPTH_TEST);

    //Normalize rot axis
    float axisNorm = sqrtf(rotAxis[0]*rotAxis[0] + rotAxis[1]*rotAxis[1] + rotAxis[2]*rotAxis[2]);
    rotAxis[0] =  rotAxis[0] / axisNorm;
    rotAxis[1] =  rotAxis[1] / axisNorm;
    rotAxis[2] =  rotAxis[2] / axisNorm;
}

void renderScene(int timeDiffMillies)
{
    angle += 0.002 * timeDiffMillies; if(angle > 2*pi) angle = 0;

    //Build a quaternion for the current angle.
    float sinHalfAngle = sinf(angle/2);
    quat[3] = cosf(angle/2);
    quat[0] = rotAxis[0] * sinHalfAngle;
    quat[1] = rotAxis[1] * sinHalfAngle;
    quat[2] = rotAxis[2] * sinHalfAngle;

    setRotationMatrx(rotationMatrix, quat);
    setTranslationMatrix(translationMatrix, cos(angle), sin(angle), -17 + 5*sin(angle));

    //Combine transformation matrices so the shader does not recalculate the combined transformation for each vertex.
    matMult4x4by4x4(transRotMatrix, translationMatrix, rotationMatrix);
    matMult4x4by4x4(modelMatrix, perspectiveMatrix, transRotMatrix);

    //Upload transformation matrix to shader.
    glUniformMatrix4fv(_modelUniform, 1, 0, modelMatrix);

    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);
}
\$\endgroup\$
  • \$\begingroup\$ Please don't modify code after receiving answers. \$\endgroup\$ – Jamal Dec 29 '14 at 0:51
2
\$\begingroup\$

I don't see why you have so many global variables. For example modelMatrix is only used renderScene() which inside its body, will pass the matrix to two other functions. Probably there are other global variables with this nature in your code.

float timeDiff = 0; is unused for example, but maybe you see it in code you don't show.


Inside buildPerspProjMat(), you have:

float ymin = -xymax;
float height = xymax - ymin;

I think that ymin variable is redundant here, since you don't use it anywhere else in the function. Change the above to this:

float height = xymax + xymax;

You have many variables that are of the same nature as ymin.


You have this:

void matMult4x4by4x4(float* m, float* a, float* b)
{
    m[0]  = a[0]*b[0]  + a[4]*b[1]  + a[8]* b[2]  + a[12]*b[3];
    m[1]  = a[1]*b[0]  + a[5]*b[1]  + a[9]* b[2]  + a[13]*b[3];
    ...
}

Here you could use a loop, with the following logic:

  int i, j, z = 0;
  for(i = 0; i < 4; ++i) {
    for(j = 0; j < 4; ++j) {
      printf("m[%d]  = a[%d]*b[%d]  + a[%d]*b[%d]  + a[%d]* b[%d]  + a[%d]*b[%d];\n",
             z, j, i * 4, j + 4, (i * 4) + 1, j + 8, (i * 4) + 2, (j + 12), (i * 4) + 3);
      ++z;
    }
  }

Maybe counter z is redundant, I am not sure.

\$\endgroup\$
  • \$\begingroup\$ Thanks for taking a look. Those timediff vars were definitely no longer used and I removed them. Thanks! The global variables would probably not be so in production, but this is a test where even the 3d object is a hard-coded global. Any thoughts on whether this is the right approach for using quaternions this way to apply rotations or the way in which the transformation matrices are prepared for upload to the shader? \$\endgroup\$ – steven Dec 29 '14 at 0:46
  • \$\begingroup\$ I don't know @steven. \$\endgroup\$ – gsamaras Dec 29 '14 at 1:19
1
\$\begingroup\$

Just little bits - nothing major except point #2.

  1. Rather than #define PI_OVER_360 0.0087266, suggest #define PI_OVER_360 (pi/360).

  2. Confident this is the wrong ratio anyways: Usually code needs 2*π/180

    #define PI (3.1415926535897932384626433832795)
    #define TWO_PI_OVER_360 (2.0*PI/360.0)
    
  3. With lots of code that does not modify the contents of elements pointed to, nice to use const to show 1) what is not being modified and 2) insures code does in fact not modify.

    // Code modifies *m, but not *a nor *b
    // void matMult4x4by4x4(float* m, float* a, float* b)
    void matMult4x4by4x4(float* m, const float* a, const float* b)
    
\$\endgroup\$
  • \$\begingroup\$ Thanks for your response. Sorry for the non-readablility on pi/360. I forgot that I had baked in some simplification (in this case 1/2 * pi/180). Point #3: you are absolutely right. \$\endgroup\$ – steven Dec 30 '14 at 20:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.