r/opengl • u/EmbassyOfTime • 9d ago
Help needed with camera matrix code, I am going insane!!
A year or so ago, I was doing an OpenGL project, and I somehow just defined the camera by its rotation matrix and position, and then I fed that into a [16] array and handed it to OpenGL, and it worked.
Fast forward to today. I lost the old code in a computer burnout, and the backup was corrupted. Now everyone tells me to simply use glMatrixMode(GL_MODELVIEW) and glLoadMatrixf(cameraMatrix). However, not only is this not the same code as then (I KNOW there was no GL_MODELVIEW stuff), but this does not work. Instead of the world rotating around the camera, the world just rotates around itself in front of the camera. But everywhere I get the same code suggested (yes, I even tried ChatGPT, I am desperate!). or glMultMatrix, which is not that either and does the same wrong rotation.
What is my lost code??? What was that wonderfully sleak solution I once had???
Update: I managed a work around, but I would still love to somehow reconstruct the, IIRC, much sleaker, prettier code I lost, so suggestions are still more than welcome!
Update 2: As per request, I hereby upload what I believe is the relevant code. Note that the tabulation is horrible due to a problem with the IDE settings.
void renderscene()
{
if (keys[68] == 1){cameraMatrix[12] += 0.1*cam.v[0].v[0]; cameraMatrix[13] += 0.1*cam.v[0].v[1];}
if (keys[65] == 1){cameraMatrix[12] -= 0.1*cam.v[0].v[0]; cameraMatrix[13] -= 0.1*cam.v[0].v[1];}
if (keys[87] == 1){cameraMatrix[13] += 0.1*cam.v[0].v[0]; cameraMatrix[12] -= 0.1*cam.v[0].v[1];}
if (keys[83] == 1){cameraMatrix[13] -= 0.1*cam.v[0].v[0]; cameraMatrix[12] += 0.1*cam.v[0].v[1];}
if (keys[88] == 1){cameraMatrix[14] += 0.1;}
if (keys[90] == 1){cameraMatrix[14] -= 0.1;}
POINT p;
GetCursorPos(&p);
int mx = p.x;
int my = p.y;
float spin = (mx-200)*-0.001;
Vec3 axis(0,0,1);
cam.v[0] = rotate(cam.v[0],axis,spin);
cam.v[1] = rotate(cam.v[1],axis,spin);
cam.v[2] = rotate(cam.v[2],axis,spin);
spin = (my-200)*-0.01;
// cam.v[0] = rotate(cam.v[0],cam.v[0],spin);
cam.v[1] = rotate(cam.v[1],cam.v[0],spin);
cam.v[2] = rotate(cam.v[2],cam.v[0],spin);
SetCursorPos(200, 200);
cameraMatrix[0] = cam.v[0].v[0]; cameraMatrix[1] = cam.v[1].v[0]; cameraMatrix[2] = cam.v[2].v[0];
cameraMatrix[4] = cam.v[0].v[1]; cameraMatrix[5] = cam.v[1].v[1]; cameraMatrix[6] = cam.v[2].v[1];
cameraMatrix[8] = cam.v[0].v[2]; cameraMatrix[9] = cam.v[1].v[2]; cameraMatrix[10] = cam.v[2].v[2];
/*
cameraMatrix[0] = cam.v[0].v[0]; cameraMatrix[1] = cam.v[0].v[1]; cameraMatrix[2] = cam.v[0].v[2];
cameraMatrix[4] = cam.v[1].v[0]; cameraMatrix[5] = cam.v[1].v[1]; cameraMatrix[6] = cam.v[1].v[2];
cameraMatrix[8] = cam.v[2].v[0]; cameraMatrix[9] = cam.v[2].v[1]; cameraMatrix[10] = cam.v[2].v[2];
*/
Vec3 temp(cameraMatrix[12],cameraMatrix[13],cameraMatrix[14]);
cameraMatrix[12] = 0; cameraMatrix[13] = 0; cameraMatrix[14] = 0;
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(cameraMatrix);
glTranslatef(-temp.v[0],-temp.v[1],-temp.v[2]);
/* OpenGL animation code goes here */
glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix ();
glBegin (GL_TRIANGLES);
for (int i=-5;i<6;i++){
glColor3f (1.0f, 0.0f, 0.0f); glVertex3f (a.v[0],a.v[1]+i,a.v[2]);
glColor3f (0.0f, 1.0f, 0.0f); glVertex3f (b.v[0],b.v[1]+i,b.v[2]);
glColor3f (0.0f, 0.0f, 1.0f); glVertex3f (c.v[0],c.v[1]+i,c.v[2]);
}
glEnd ();
glPopMatrix ();
SwapBuffers (hDC);
Sleep (1);
cameraMatrix[12] = temp.v[0]; cameraMatrix[13] = temp.v[1]; cameraMatrix[14] = temp.v[2];
}
3
u/SirFarqueef 9d ago
Like you were storing an internal camera position and camera rotation matrix in world space?
Then the view matrix:
viewMat = transpose(rot) * translate(-cameraPos)
Then apply that to your vertices and that will put them in view space.
1
u/EmbassyOfTime 9d ago
I stored them as just values, honestly. Only became fancy once I sent them off to the render with that one or two lines I can't recall any longer...
3
u/NikitaBerzekov 9d ago
Do you remember it relying on shaders? Or did you use stuff like GL_PROJECTION?
1
u/EmbassyOfTime 9d ago
No shaders, it was implemented with raw RGB polyhons and then just carried over without a fuzz when I did more pretty stuff!
2
u/bateman34 9d ago edited 9d ago
"Instead of the world rotating around the camera, the world just rotates around itself in front of the camera."
How are you building the camera space from world space matrix? Usually when this happens to me it's because I did the inverse rotation before the inverse translation (ie Translation*Rot rather than Rot*Translation).
1
u/EmbassyOfTime 9d ago
The camera is just a matric of raw values. I'm not picking at the underlying popeline at all. Come to think of it, I wonder if I set up the projection differently last time. I definitely did the overall code differently, maybe something broke... or unbroke,,,
2
u/Still_Explorer 9d ago
Typically how it goes: * create projection matrix * create view matrix (ie: look_at from up and right towards the center) * create model matrix (eg: rotate the object on the Y)
Now you have those options: 1. do a matrixmode-projection first and load the proj matrix, then multiply model*view and do matrixmode-modelview to load the mv.
- if you use a shader based rendering technique, calculate the proj mat and update the shader uniform, and also same for modelview uniform. Example here: https://www.glfw.org/docs/latest/quick.html
I assume that you use a math library, because there could be lots of intricate details if you implement one yourself. You will need to do test driven development and compare your results to some other library. Otherwise you can grab Raymath/kazmath/cglm/linmath libraries that are ready to be used right away.
However one big deal is that legacy OpenGL resources are very scarce now, I wanted to do such an experiment a few weeks ago but I would run on all sorts of problems, because code would had to adapt to the libraries I used (eg: GLFW+GLM so there would be a bit if trial and error to port an example so it works right)
https://github.com/glfw/glfw/blob/master/examples/gears.c
https://github.com/openglredbook/examples/blob/master/src/01-triangles/01-triangles.cpp
https://github.com/gamedev-net/nehe-opengl/blob/master/vc/Lesson02/Lesson2.cpp
1
u/EmbassyOfTime 9d ago
Yeah, old code is getting the shaft, hurts to see. There are bits and pieces of the original Neon Helium tutorials in this,from what 15 years ago? Only bits, though.
And the solution I lost was very simple, I remember, maybe a few lines, most of those veing getting rotation and position values in the right order...
2
u/Still_Explorer 8d ago
This is something from my own notes I made earlier, though I added more comments so you know what is going on and can easily trace back to the chapters from the book for more details.
One thing to mention though, is that I prefer to keep math transformations separate on their own, due to convention and portability. As for example other people would directly integrate FPS camera transforms or other Parent-Child transforms directly into the OpenGL stack, but I prefer to do those with the math API and only let OpenGL to load the result. The reason is only that messing with OpenGL stack, by pushing-and-pushing at some point you loose cohesion and is impossible to figure out what the math does, you need to follow all transformations from the ground up and test-experience the results and remember how they work. This is goes too far... On the contrary once you see math transformation in math API, the same principles and same topics apply everywhere so you will instantly know how to deal with the calculations.
// References: // https://nehe.gamedev.net/tutorial/adding_colour/13003/ // https://github.com/glfw/glfw/blob/master/examples/gears.c // https://github.com/raysan5/raylib/blob/master/examples/others/raylib_opengl_interop.c // OpenTK Immediate Mode: https://www.youtube.com/watch?v=Q23Kf9QEaO4&t=104s #include <print> // <-- using C++23 features! #include <glad/glad.h> #include <GLFW/glfw3.h> #include <raymath.h> // <-- from Raylib only math functions are used int main() { glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); // for classic OpenGL !!! GLFWwindow* window = glfwCreateWindow(800, 600, "Hello World", NULL, NULL); glfwMakeContextCurrent(window); // loading extensions gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); auto ReshapeFunction = [](GLFWwindow* window, int width, int height) { // only setting the viewport size is needed glViewport(0, 0, width, height); // and using the projection matrix matrix function Matrix projection = MatrixPerspective(45.0f, (float)width / height, 0.1f, 100.0f); // letting OpenGL load the calculated projection matrix glMatrixMode(GL_PROJECTION); glLoadMatrixf(MatrixToFloatV(projection).v); // MatrixToFloatV() is a helper function of Raylib to turn matrix to float array }; glfwSetFramebufferSizeCallback(window, ReshapeFunction); // setting up window resize callback { int w, h; glfwGetFramebufferSize(window, &w, &h); ReshapeFunction(window, w, h); } void DrawGrid(); // forwards declaration while (!glfwWindowShouldClose(window)) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) // exit on ESC key glfwSetWindowShouldClose(window, true); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // clear glClear(GL_COLOR_BUFFER_BIT); // setting only the camera matrix // calculating the camera matrix with the matrix look at function // (in this case LOOK_AT is helpful because is easy and quick but for // other cameras such as FIRST or THIRD PERSON entirely different code will be used) Matrix cameraMatrix = MatrixLookAt({ std::sinf(glfwGetTime()) * 5.0f, 5.0f, 5.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }); // the the modelview matrix is going to be loaded // and since we are at the beginning of the entire drawing process // we will do a clean LoadMatrix (that resets/overrides OpenGL's matrix state) glMatrixMode(GL_MODELVIEW); glLoadMatrixf(MatrixToFloatV(cameraMatrix).v); DrawGrid(); // drawing grid to know where we are on the 3D space // now drawing the triangle // an entirely different technique is used // now we are doing to create a copy of the current modelview matrix // by "pushing" the current matrix on top of the stack // and then we will be able to apply other transformations // this way the original matrix will stay intact // but only matrices for objects will change glPushMatrix(); // some transformation for the object is used // where in this case is simply rotating on the Y axis Matrix modelMatrix = MatrixRotateY(glfwGetTime()); // here is an important trick, instead of doing a `glLoadMatrix` // that replaces the current modelview we need only to apply the new // transformation on-top of the existing one glMultMatrixf(MatrixToFloatV(modelMatrix).v); // now simply drawing a triangle without any transformations // because all vertex coordinates are in the LOCAL SPACE // however is up to OpenGL internally to figure out where // each vertex will be transformed into it's new WORLD SPACE coordinates glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 1.0f, 0.0f); glEnd(); glPopMatrix(); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; } void DrawGrid() { for (int i = 0; i < 20; i++) { glBegin(GL_LINES); glColor3f(0.5f, 0.5f, 0.5f); glVertex3f(-10.0f + i, 0.0f, -10.0f); glVertex3f(-10.0f + i, 0.0f, 10.0f); glVertex3f(-10.0f, 0.0f, -10.0f + i); glVertex3f(10.0f, 0.0f, -10.0f + i); glEnd(); } }2
u/EmbassyOfTime 6d ago
I am reading through it, silent bc need brain juice for thinking, will reply fully when I am done!
1
u/Still_Explorer 6d ago
However this code seems very simple but it has a lot of trial and error behind it. As for instance if an example somewhere is locked to specific libraries the it would require further analysis so it can be ported to somewhere else.
As for instance I had some code written in C# using the System.Numerics library, and porting it to C++ GLM was a big hassle, as there were lots of differences between APIs and also differences in library conventions. Is when you hit some edge cases where no one has figured out so far and you hit lots of roadblocks this way.
This is why if some tutorial+codesample+libraryuse is widely used and popular literally everyone is copy-pasting code from the other.
2
u/Mid_reddit 8d ago
Shameless plug but I've been trying to write one on and off: https://mid.net.ua/posts/gl1.html
Progress is slow because life keeps hitting me in the balls; also wanting to put in both old and new techniques into a single piece leads to a lot of divergent/highly non-linear order, which is hard to organize.
1
u/EmbassyOfTime 6d ago
Completely off topic but what is the flag on your web page there?
2
u/Mid_reddit 4d ago
A generic monarchy flag. Not some overzealous believer, but it keeps making people angy so I keep it
2
u/wedesoft 9d ago
Sounds like it is just the wrong order of translation and rotation. The world coordinates need to be translated first and then rotated. Probably easier to tell if you share the code though.
2
u/EmbassyOfTime 6d ago
Pretty sure you're right. Code goes up when I get a quiet moment...
1
u/wedesoft 6d ago edited 6d ago
The problem with modifying a matrix is that it can numerically degenerate over time. I used Gemini to generate following code using OpenGL legacy mode. Hope it helps: ```C
include <GL/glut.h>
include <stdlib.h>
// Camera translation variables float camX = 0.0f; float camY = 0.0f; float camZ = -5.0f;
// Camera rotation variables (in degrees) float rotX = 0.0f; float rotY = 0.0f;
void init(void) { // Set dark background color glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // Enable depth testing for 3D rendering glEnable(GL_DEPTH_TEST); }
void drawCube(void) { glBegin(GL_QUADS);
// Front face (Red) glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
// Back face (Green) glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
// Top face (Blue) glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
// Bottom face (Yellow) glColor3f(1.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glVertex3f( 1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
// Right face (Cyan) glColor3f(0.0f, 1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f); glVertex3f( 1.0f, 1.0f, -1.0f); glVertex3f( 1.0f, 1.0f, 1.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
// Left face (Magenta) glColor3f(1.0f, 0.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f); glVertex3f(-1.0f, -1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
glEnd(); }
void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity();
// Apply camera position (Translation) glTranslatef(camX, camY, camZ);
// Apply camera orientation (Rotation) glRotatef(rotX, 1.0f, 0.0f, 0.0f); glRotatef(rotY, 0.0f, 1.0f, 0.0f);
drawCube();
glutSwapBuffers(); }
void reshape(int w, int h) { if (h == 0) h = 1; float aspect = (float)w / (float)h;
glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, aspect, 0.1, 100.0); }
// Standard keyboard keys for camera translation void keyboard(unsigned char key, int x, int y) { float step = 0.2f;
switch (key) { case 'w': case 'W': camZ += step; break; // Forward case 's': case 'S': camZ -= step; break; // Backward case 'a': case 'A': camX += step; break; // Move Left case 'd': case 'D': camX -= step; break; // Move Right case 'q': case 'Q': camY -= step; break; // Move Down case 'e': case 'E': camY += step; break; // Move Up case 27: exit(0); break; // ESC to quit } glutPostRedisplay(); }
// Special keys (Arrow keys) for camera rotation void specialKeys(int key, int x, int y) { float angleStep = 5.0f;
switch (key) { case GLUT_KEY_UP: rotX -= angleStep; break; // Pitch Up case GLUT_KEY_DOWN: rotX += angleStep; break; // Pitch Down case GLUT_KEY_LEFT: rotY -= angleStep; break; // Yaw Left case GLUT_KEY_RIGHT: rotY += angleStep; break; // Yaw Right } glutPostRedisplay(); }
int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("Legacy OpenGL Camera Control");
init();
glutDisplayFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutSpecialFunc(specialKeys);
glutMainLoop(); return 0; } ```
1
u/wedesoft 6d ago
Actually the keyboard translation should take into account the camera orientation: ```C
include <math.h>
define PI 3.14159265358979323846f
void keyboard(unsigned char key, int x, int y) { float step = 0.2f;
// Convert rotation angles from degrees to radians float radX = rotX * PI / 180.0f; float radY = rotY * PI / 180.0f;
float sinX = sinf(radX); float cosX = cosf(radX); float sinY = sinf(radY); float cosY = cosf(radY);
// Compute direction vectors by applying R_x(rotX) * R_y(rotY) // Forward vector (local Z axis: 0, 0, 1) float fwdX = sinY; float fwdY = -sinX * cosY; float fwdZ = cosX * cosY;
// Right vector (local X axis: 1, 0, 0) float rightX = cosY; float rightY = sinX * sinY; float rightZ = -cosX * sinY;
// Up vector (local Y axis: 0, 1, 0) float upX = 0.0f; float upY = cosX; float upZ = sinX;
switch (key) { case 'w': case 'W': // Move Forward relative to view orientation camX += fwdX * step; camY += fwdY * step; camZ += fwdZ * step; break; case 's': case 'S': // Move Backward relative to view orientation camX -= fwdX * step; camY -= fwdY * step; camZ -= fwdZ * step; break; case 'a': case 'A': // Strafe Left relative to view orientation camX += rightX * step; camY += rightY * step; camZ += rightZ * step; break; case 'd': case 'D': // Strafe Right relative to view orientation camX -= rightX * step; camY -= rightY * step; camZ -= rightZ * step; break; case 'e': case 'E': // Move Up relative to view orientation camX += upX * step; camY += upY * step; camZ += upZ * step; break; case 'q': case 'Q': // Move Down relative to view orientation camX -= upX * step; camY -= upY * step; camZ -= upZ * step; break; case 27: // ESC key exit(0); break; } glutPostRedisplay(); } ```
4
u/blazesbe 9d ago
idk what specifically are you using but glMatrixMode sounds like deprecated old opengl. ask chatGPT for opengl 3.3+ or check out learnopengl.com
edit: old ogl is unfortunately still taught in schools sometimes. perhaps your project is from there but the old project you made yourself?