Search code examples
cmath3dsdlsdl-2

How can I implement 3d graphics rendering in C with the SDL2?


I tried implementing a C program which draws a 3d cube using SDL2. Im sorry if it is just a stupid small mistake I'm very new to C/C++. I followed this tutorial: https://youtu.be/ih20l3pJoeU. The guy from the tutorial coded everything in C++ but I tried coding it in C. But there's a problem with my code. Only parts of the Cube are drawn and it looks really weird. Here is all my code:

main.c:

#include "window.h"

int main() {
    window_init();
    window_startloop();

    return 0;
}

window.c:

#include "window.h"

#define false 0
#define true 1



void MultMatrixVec(vec3 *i, vec3 *o, mat4x4 *m) {
    o->x = i->x * m->m[0][0] + i->y * m->m[1][0] + i->z * m->m[2][0] + m->m[3][0];
    o->y = i->x * m->m[0][1] + i->y * m->m[1][1] + i->z * m->m[2][1] + m->m[3][1];
    o->z = i->x * m->m[0][2] + i->y * m->m[1][2] + i->z * m->m[2][2] + m->m[3][2];
    float w = i->x * m->m[0][3] + i->y * m->m[1][3] + i->z * m->m[2][3] + m->m[3][3];

    if (w != 0) {
        o->x /= w;
        o->y /= w;
        o->z /= w;
    }
}

int DrawTriangle(SDL_Renderer* p_renderer, int x1, int y1, int x2, int y2, int x3, int y3, Uint8 p_r, Uint8 p_g, Uint8 p_b, Uint8 p_a) {

    Uint8 r, g, b, a;

    SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a);
    SDL_SetRenderDrawColor(renderer, p_r, p_g, p_b, p_a);

    
    SDL_Point points[3] = { { x1, y1 }, { x2, y2 }, { x3, y3 } };


    int ret = SDL_RenderDrawLines(p_renderer, &points, 3);

    SDL_SetRenderDrawColor(renderer, r, g, b, a);

    return ret;
}

int sdl_init() {
    if (SDL_Init(SDL_INIT_EVERYTHING) || SDL_InitSubSystem(SDL_INIT_EVERYTHING)) {
        printf("Error at initializing SDL2 Error: %s", SDL_GetError());
        return 1;
    }

    return 0;
}

int window_init() {
    if (sdl_init()) {
        return 1;
    }

    window = SDL_CreateWindow(WINDOW_TITLE, NULL, NULL, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_FULLSCREEN);
    renderer = SDL_CreateRenderer(window, -1, 0);
        
    triangle cubeTris[] = {

        // SOUTH
        { 0.0f, 0.0f, 0.0f,    0.0f, 1.0f, 0.0f,    1.0f, 1.0f, 0.0f },
        { 0.0f, 0.0f, 0.0f,    1.0f, 1.0f, 0.0f,    1.0f, 0.0f, 0.0f },

        // EAST                                                      
        { 1.0f, 0.0f, 0.0f,    1.0f, 1.0f, 0.0f,    1.0f, 1.0f, 1.0f },
        { 1.0f, 0.0f, 0.0f,    1.0f, 1.0f, 1.0f,    1.0f, 0.0f, 1.0f },

        // NORTH                                                     
        { 1.0f, 0.0f, 1.0f,    1.0f, 1.0f, 1.0f,    0.0f, 1.0f, 1.0f },
        { 1.0f, 0.0f, 1.0f,    0.0f, 1.0f, 1.0f,    0.0f, 0.0f, 1.0f },

        // WEST                                                      
        { 0.0f, 0.0f, 1.0f,    0.0f, 1.0f, 1.0f,    0.0f, 1.0f, 0.0f },
        { 0.0f, 0.0f, 1.0f,    0.0f, 1.0f, 0.0f,    0.0f, 0.0f, 0.0f },

        // TOP                                                       
        { 0.0f, 1.0f, 0.0f,    0.0f, 1.0f, 1.0f,    1.0f, 1.0f, 1.0f },
        { 0.0f, 1.0f, 0.0f,    1.0f, 1.0f, 1.0f,    1.0f, 1.0f, 0.0f },

        // BOTTOM                                                    
        { 1.0f, 0.0f, 1.0f,    0.0f, 0.0f, 1.0f,    0.0f, 0.0f, 0.0f },
        { 1.0f, 0.0f, 1.0f,    0.0f, 0.0f, 0.0f,    1.0f, 0.0f, 0.0f },

    };

    meshCube.tris = cubeTris;

    float fNear = 0.1f;
    float fFar = 1000.f;
    float fFov = 90;
    float fAspectRatio = (float)WINDOW_HEIGHT / (float)WINDOW_WIDTH;
    float fFovRad = 1.f / tanf(fFov / 2.f / 180.f * 3.14159f);

    matProj.m[0][0] = fAspectRatio * fFovRad;
    matProj.m[1][1] = fFovRad;
    matProj.m[2][2] = fFar / (fFar - fNear);
    matProj.m[2][3] = 1.f;
    matProj.m[3][3] = 0.f;

    isRunning = true;
    return 0;
}

void window_destroy() {
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    isRunning = false;

    return;
}

void render() {
    SDL_RenderClear(renderer);

    for (int i = 0; i < sizeof(meshCube.tris); i++) {
        triangle triProjected, triTranslated;

        triTranslated = meshCube.tris[i];
        triTranslated.p[0].z = meshCube.tris[i].p[0].z + 3.f;
        triTranslated.p[1].z = meshCube.tris[i].p[0].z + 3.f;
        triTranslated.p[2].z = meshCube.tris[i].p[0].z + 3.f;

        MultMatrixVec(&triTranslated.p[0], &triProjected.p[0], &matProj);
        MultMatrixVec(&triTranslated.p[1], &triProjected.p[1], &matProj);
        MultMatrixVec(&triTranslated.p[2], &triProjected.p[2], &matProj);

        triProjected.p[0].x += 1.f;
        triProjected.p[0].y += 1.f;
        triProjected.p[1].x += 1.f;
        triProjected.p[1].y += 1.f;
        triProjected.p[2].x += 1.f;
        triProjected.p[2].y += 1.f;

        triProjected.p[0].x *= 0.5f * (float) WINDOW_WIDTH;
        triProjected.p[0].y *= 0.5f * (float)WINDOW_HEIGHT;
        triProjected.p[1].x *= 0.5f * (float)WINDOW_WIDTH;
        triProjected.p[1].y *= 0.5f * (float)WINDOW_HEIGHT;
        triProjected.p[2].x *= 0.5f * (float)WINDOW_WIDTH;
        triProjected.p[2].y *= 0.5f * (float)WINDOW_HEIGHT;

        DrawTriangle(renderer, triProjected.p[0].x, triProjected.p[0].y, 
            triProjected.p[1].x, triProjected.p[1].y,
            triProjected.p[2].x, triProjected.p[2].y, 255, 255, 255, 255);
    }

    SDL_RenderPresent(renderer);
}

void window_loop() {
    render();

    SDL_Event e;
    while (SDL_PollEvent(&e) > 0) {
        switch (e.type) {
        case SDL_QUIT:
            window_destroy();
            break;
        }
    }

    return;
}

void window_startloop() {
    while (isRunning) {
        window_loop();
    }

    return;
}

window.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include "3dmath.h"

#define WINDOW_TITLE "Epic 3d Game"
#define WINDOW_WIDTH 1920
#define WINDOW_HEIGHT 1080

SDL_Window* window;
SDL_Renderer* renderer;
int isRunning;
mesh meshCube;
mat4x4 matProj;
float fTheta;

int window_init();
void window_startloop();
void window_destroy();

3dmath.h:

#pragma once

typedef struct {
    float x, y, z;
} vec3;

typedef struct {
    vec3 p[3];
} triangle;

typedef struct {
    triangle* tris;
} mesh;

typedef struct {
    float m[4][4];
} mat4x4;

First I checked if my Cube Verticies Coords are correct and they were correct. Then I deleted many parts of the code and tried to write it again while following along with the tutorial but nothing worked.


Solution

  • Supposing you've handled your global bariables in a more sensible way than written in quiestion, problems I've noticed so far:

    • DrawTriangle passes 3 points to SDL_RenderDrawLines. To draw a triangle, you have to draw 3 lines, and each line consists of 2 points, so total number of points in triangle is 6:
        SDL_Point points[] = {
            {x1, y1}, {x2, y2},
            {x2, y2}, {x3, y3},
            {x3, y3}, {x1, y1},
        };
    
    
        int ret = SDL_RenderDrawLines(p_renderer, &points, sizeof(points)/sizeof(points[0]));
    
    • in render, you iterate over sizeof(meshCube.tris), but sizeof of any pointer type is sizeof(void*) (e.g. 8 or 4 or 32bit system). You need to save number of vertices, e.g. add int num_tris to your mesh structure and fill it with meshCube.num_tris = sizeof(cubeTris) / sizeof(cubeTris[0]); in your window_init, where cubeTris is not a pointer but an actual array

    • you set tris as pointer to temporary array of triangles. When you render, this array may be stomped already. You have to either allocate space for that data or set it as global/static. Declaring it as static triangle cubeTris[] = { .... } would suffice

    As a side note, with more-or-less modern C you don't need true/false defines, as we have #include <stdbool.h> since C99. Also note that in C, in contrast to C++, void foo() is not function that takes no arguments but rather function that takes unspecified number of arguments; function thaking no arguments would be void foo(void). [this should no longer be true with recent C2x, which is to be released "soon" as C23]