For various reasons I am building a very simple graphics engine. I have a pretty good 2D thing using plain SDL2 and C that essentially boils down to a single "putpixel" function. I can create a window of arbitrary dimensions and draw pixels wherever I want. It's rad.
I would like to expand it to enable me to draw 3D stuff. I have dabbled with OpenGL before and have drawn cubes and triangles so I understand the concepts. For different reasons, I can't use OpenGL currently, and I thought it would be fun anyway to make something "from scratch". And now I'm stuck.
I have a standard cube:
typedef struct {
float x;
float y;
float z;
} vec3;
typedef struct {
float x;
float y;
float z;
float w;
} vec4;
vec3 mesh[36] = {
{-1.0f, -1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, {1.0f, 1.0f, -1.0f},
{1.0f, 1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}, {-1.0f, -1.0f, -1.0f},
{-1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, 1.0f},
{1.0f, 1.0f, 1.0f}, {1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f},
... etc
I have a matrix struct:
typedef struct {
float e00; float e01; float e02; float e03;
float e10; float e11; float e12; float e13;
float e20; float e21; float e22; float e23;
float e30; float e31; float e32; float e33;
} mat4;
I also have a vec4
, and some functions to do the basic maths:
vec4 vec3_multiply_mat4(vec3 vector, mat4 matrix); // lazy hardcodes the w as 1
vec4 vec4_multiply_mat4(vec4 vector, mat4 matrix);
mat4 mat4_multiply_mat4(mat4 a, mat4 b);
So. Where I'm stuck - I know that in order to convert my universal device co-ords for my mesh, into pixel coordinates, I need 3 matrices: model, view, projection. I have defined my projection matrix because this seems to be pretty well documented. I'm using a perspective matrix:
projection = perspective(to_radians(90.0f), (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f);
mat4 perspective(float fov, float aspect, float near, float far) {
float tan_fov = tanf(fov / 2.0f);
mat4 m = {
1.0f / (aspect * tan_fov), 0.0f, 0.0f, 0.0f,
0.0f, 1.0f / tan_fov, 0.0f, 0.0f,
0.0f, 0.0f, -( (far + near) / (far - near) ), -( (2.0f * far * near) / (far - near) ),
0.0f, 0.0f, -1.0f, 0.0f,
};
return m;
}
I'm lost on how to properly define the model (world?) and view (camera?) matrices to properly control how my cube is displayed. For testing, I've set my view matrix to the identity matrix, and my model matrix to scale the cube to half (poking numbers in for a bit got me to this point, and it displays something on the screen).
mat4 model = {
0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
mat4 view = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
Here's my draw:
void draw(void* arg) {
pixel_cls(palette[DARK_GRAY]);
vec4 point;
for (uint32_t i=0 ; i<36 ; i++) {
point = vec3_multiply_mat4(mesh[i], model);
point = vec4_multiply_mat4(point, view);
point = vec4_multiply_mat4(point, projection);
point.x += 1.0;
point.y += 1.0;
point.x *= 0.5f * WIDTH;
point.y *= 0.5f * HEIGHT;
object[i] = point;
}
for (uint32_t i=0 ; i<36/3 ; i++) {
pixel_line(object[i*3].x, object[i*3].y, object[i*3+1].x, object[i*3+1].y, palette[WHITE]);
pixel_line(object[i*3+1].x, object[i*3+1].y, object[i*3+2].x, object[i*3+2].y, palette[WHITE]);
pixel_line(object[i*3+2].x, object[i*3+2].y, object[i*3].x, object[i*3].y, palette[WHITE]);
}
}
Which gets me 2 triangles. It appears to be drawing the cube, but I'm looking directly at one face, so everything essentially lines up.
I don't understand how to define the model & view matrices in a way that allow me to move things around and look from different angles. I also don't understand why I had to hack in some extra math to make it work (I was following a video elsewhere but I'm missing something somewhere) - this should be handled in one of the matrices no?:
point.x += 1.0;
point.y += 1.0;
point.x *= 0.5f * WIDTH;
point.y *= 0.5f * HEIGHT;
I know what I have to do. I just can't quite work out how to do it!
I'm lost on how to properly define the model (world?) and view (camera?) matrices to properly control how my cube is displayed.
For the most part, those two matrices can start asidentity
matrices, which your engine can then use to move the camera or individual objects around. In order to view your cube in front of the camera, you might need to translate one of these matrices (Something likeglTranslatef(0, 0, -5)
), so that the cube is drawn in front of the camera, instead of on top of it (making it most likely invisible). \$\endgroup\$