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);
}