如何使用 SDL2 在 C 语言中实现 3D 图形渲染?

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

提问人:Slykapar 提问时间:6/6/2023 最后编辑:Slykapar 更新时间:6/7/2023 访问量:338

问:

我尝试实现一个 C 程序,该程序使用 SDL2 绘制 3D 立方体。对不起,如果这只是一个愚蠢的小错误,我对 C/C++ 非常陌生。我遵循了这个教程:https://youtu.be/ih20l3pJoeU。教程中的那个人用 C++ 编码了所有内容,但我尝试用 C 编码。 但是我的代码有问题。只绘制了立方体的一部分,看起来真的很奇怪。这是我所有的代码:

main.c:

#include "window.h"

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

    return 0;
}

窗口.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;

首先,我检查了我的立方体顶点坐标是否正确,它们是正确的。然后我删除了代码的许多部分,并尝试在遵循教程的同时再次编写它,但没有任何效果。

C 数学 3D SDL SDL-2

评论


答:

1赞 keltar 6/7/2023 #1

假设您以比静止方式更明智的方式处理了全局可引用性,到目前为止我注意到的问题:

  • DrawTriangle将 3 个点传递给 。要画一个三角形,你必须画 3 条线,每条线由 2 个点组成,所以三角形的总点数是 6:SDL_RenderDrawLines
    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]));
  • 在 中,您可以遍历 ,但任何指针类型的 sizeof 都是(例如 8 位、4 位或 32 位系统)。您需要保存顶点的数量,例如添加到网格结构中并用 填充它,其中不是指针而是实际数组rendersizeof(meshCube.tris)sizeof(void*)int num_trismeshCube.num_tris = sizeof(cubeTris) / sizeof(cubeTris[0]);window_initcubeTris

  • 设置为指向三角形临时数组的指针。渲染时,此数组可能已经被踩踏。您必须为该数据分配空间或将其设置为全局/静态。声明它就足够了trisstatic triangle cubeTris[] = { .... }

顺便说一句,使用或多或少的现代 C 语言,您不需要真/假定义,就像我们自 C99 以来所做的那样。另请注意,与 C++ 相比,在 C 中,不是不带参数的函数,而是带不指定数量的参数的函数;函数解冻没有参数将是 .[对于最近的 C2x,这应该不再是事实,它将“很快”作为 C23 发布]#include <stdbool.h>void foo()void foo(void)

评论

0赞 Slykapar 6/7/2023
“以更明智的方式处理您的全球可负担性”是什么意思?我该怎么做?编辑:“meshCube.num_tris = sizeof(cubeTris) / sizeof(cubeTris[0]);”该行中的 / 是“或”还是除法?
0赞 keltar 6/7/2023
这是一个分裂。 是获取数组中元素数量的常用方法,如果它的语言认为它是一个实际的数组(如果它还没有衰减到指针)sizeof(array)/sizeof(array[0])
0赞 keltar 6/7/2023
好吧,你的头文件中有全局变量。此标头由两个文件包含,因此每个文件都定义其全局变量集。这将导致链接器错误。但是,由于这不是问题的一部分,我认为您以其他方式处理。.c