Here is the source code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <glm.h>
#include <SDL3/SDL.h>
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
struct {
int nposition;
int nnormal;
int ntexcoord;
int nface;
int ncolor;
vec3 positions[1 << 14];
vec2 normals[1 << 14];
vec2 texcoords[1 << 14];
vec3 faces[1 << 14];
vec3 colors[1 << 14];
vec3 buffer[1 << 14];
vec3 cbuffer[480][640];
float dbuffer[480][640];
uint32_t mcbuffer[480][640];
}
data;
int init(void)
{
SDL_SetAppMetadata("Graphics", "1.0", "com.graphics");
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_Log("Failed to init SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
if (!SDL_CreateWindowAndRenderer("graphics", 640, 480, 0, &window, &renderer))
{
SDL_Log("Failed to init window / renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB96_FLOAT, SDL_TEXTUREACCESS_STREAMING, 640, 480);
if (!texture)
{
SDL_Log("Failed to create texture: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
return SDL_APP_CONTINUE;
}
int app_event(SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT)
return SDL_APP_SUCCESS;
return SDL_APP_CONTINUE;
}
int app_iterate(void)
{
void triangle_scanline(vec2 *, uint8_t *);
void triangle_edge(vec2 *, uint8_t *);
void render_obj(void);
SDL_RenderClear(renderer);
render_obj();
SDL_RenderPresent(renderer);
SDL_Delay(10);
return SDL_APP_CONTINUE;
}
void quit(void) {}
void triangle_scanline(vec2 t[3], uint8_t col[3])
{
SDL_SetRenderDrawColor(renderer, col[0], col[1], col[2], 255);
if (t[0][1] > t[1][1]) glm_vec2_swap(t[0], t[1]);
if (t[0][1] > t[2][1]) glm_vec2_swap(t[0], t[2]);
if (t[1][1] > t[2][1]) glm_vec2_swap(t[1], t[2]);
float s1 = (t[1][0] - t[0][0]) / (t[1][1] - t[0][1]);
float s2 = (t[2][0] - t[0][0]) / (t[2][1] - t[0][1]);
float s3 = (t[2][0] - t[1][0]) / (t[2][1] - t[1][1]);
float x1 = t[0][0], x2 = t[0][0];
for (int y = t[0][1]; y < t[1][1]; y++)
{
SDL_RenderLine(renderer, x1, y, x2, y);
x1 += s1; x2 += s2;
}
for (int y = t[1][1]; y < t[2][1]; y++)
{
SDL_RenderLine(renderer, x1, y, x2, y);
x1 += s3; x2 += s2;
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
}
float edge(vec2 a, vec2 b, vec2 p)
{
return -1 * ((p[0] - a[0]) * (b[1] - a[1]) - (p[1] - a[1]) * (b[0] - a[0])) / 2;
}
void triangle_edge(vec3 t[3], uint8_t col[3])
{
int xmin = min(min(t[0][0], t[1][0]), t[2][0]);
int xmax = max(max(t[0][0], t[1][0]), t[2][0]);
int ymin = min(min(t[0][1], t[1][1]), t[2][1]);
int ymax = max(max(t[0][1], t[1][1]), t[2][1]);
for (int y = ymin; y < ymax; y++)
for (int x = xmin; x < xmax; x++)
{
vec2 p = {x, y};
float s1 = edge(t[0], t[1], p);
float s2 = edge(t[1], t[2], p);
float s3 = edge(t[2], t[0], p);
if (s1 >= 0 && s2 >= 0 && s3 >= 0)
{
float s = s1 + s2 + s3;
if (s == 0) continue;
float invs = 1 / s;
vec3 weights = {s1 * invs, s2 * invs, s3 * invs};
vec3 depths = {t[0][2], t[1][2], t[2][2]};
vec3 vcol = {col[0], col[1], col[2]};
float depth = glm_vec3_dot(weights, depths);
if (depth > data.dbuffer[y][x]) continue;
glm_vec3_copy(vcol, data.cbuffer[y][x]);
data.dbuffer[y][x] = depth;
}
}
}
int parse_obj(char *filename)
{
FILE *f = fopen(filename, "r");
if (!f)
{
printf("failed to parse obj file: file does not exist\n");
return 0;
}
char s[16];
while (fscanf(f, "%s", &s) > 0)
{
if (!strcmp(s, "v"))
{
int fields = fscanf(f, "%f %f %f\n",
&data.positions[data.nposition][0],
&data.positions[data.nposition][1],
&data.positions[data.nposition][2]
);
if (fields < 3)
{
printf("failed to parse obj file: invalid position format\n");
return 0;
}
data.nposition++;
}
else if (!strcmp(s, "vn"))
{
int fields = fscanf(f, "%f %f\n",
&data.normals[data.nnormal][0],
&data.normals[data.nnormal][1]
);
if (fields < 2)
{
printf("failed to parse obj file: invalid normal format\n");
return 0;
}
data.nnormal++;
}
else if (!strcmp(s, "vt"))
{
int fields = fscanf(f, "%f %f\n",
&data.texcoords[data.ntexcoord][0],
&data.texcoords[data.ntexcoord][1]
);
if (fields < 2)
{
printf("failed to parse obj file: invalid texcoord format\n");
return 0;
}
data.ntexcoord++;
}
else if (!strcmp(s, "f"))
{
int fields = fscanf(f, "%f/%f/%f %f/%f/%f %f/%f/%f\n",
&data.faces[data.nface][0],
&data.faces[data.nface][1],
&data.faces[data.nface][2],
&data.faces[data.nface+1][0],
&data.faces[data.nface+1][1],
&data.faces[data.nface+1][2],
&data.faces[data.nface+2][0],
&data.faces[data.nface+2][1],
&data.faces[data.nface+2][2]
);
if (fields < 3)
{
printf("failed to parse obj file: invalid face format\n");
return 0;
}
data.nface += 3;
}
else
{
char str[1 << 10];
fgets(str, (1 << 10), f);
}
}
for (int i = 0; i < data.nface; i++)
glm_vec3_copy(
(vec3){
rand() % 255, rand() % 255, rand() % 255
},
data.colors[i]
);
return 1;
}
void print_obj(void)
{
for (int i = 0; i < data.nposition; i++)
printf("%f %f %f\n",
data.positions[i][0],
data.positions[i][1],
data.positions[i][2]);
for (int i = 0; i < data.nnormal; i++)
printf("%f %f\n",
data.normals[i][0],
data.normals[i][1]);
for (int i = 0; i < data.ntexcoord; i++)
printf("%f %f\n",
data.texcoords[i][0],
data.texcoords[i][1]);
for (int i = 0; i < data.nface; i++)
printf("%4.0f %4.0f %4.0f\n",
data.faces[i][0],
data.faces[i][1],
data.faces[i][2]);
}
float rad(float theta) { return theta * 3.1415 / 180; }
void render_obj(void)
{
static float theta = 0.0f;
// theta++;
vec3 ihat_yaw = {cos(rad(theta)), 0, sin(rad(theta))};
vec3 jhat_yaw = {0, 1, 0};
vec3 khat_yaw = {-sin(rad(theta)), 0, cos(rad(theta))};
vec3 ihat_pitch = {1, 0, 0};
vec3 jhat_pitch = {0, cos(rad(theta)), -sin(rad(theta))};
vec3 khat_pitch = {0, sin(rad(theta)), cos(rad(theta))};
for (int i = 0; i < data.nposition; i++)
{
vec3 v;
glm_vec3_copy(data.positions[i], v);
glm_vec3_transform(ihat_yaw, jhat_yaw, khat_yaw, v);
glm_vec3_add(v, (vec3){0, 0, 5}, v);
glm_vec3_copy(v, data.buffer[i]);
}
for (int i = 0; i < data.nposition; i++)
{
data.buffer[i][0] =
(data.buffer[i][0] *
(480 / (2 * tan(rad(30 / 2))) / data.buffer[i][2]))
+ (640 / 2);
data.buffer[i][1] =
480 - ((data.buffer[i][1] *
(480 / (2 * tan(rad(30 / 2))) / data.buffer[i][2]))
+ (480 / 2));
}
for (int i = 0; i < data.nface; i += 3)
{
vec3 t[3];
glm_vec3_copy(data.buffer[(int) data.faces[i][0] - 1], t[0]);
glm_vec3_copy(data.buffer[(int) data.faces[i+1][0] - 1], t[1]);
glm_vec3_copy(data.buffer[(int) data.faces[i+2][0] - 1], t[2]);
triangle_edge(
t,
(uint8_t [3]) {
data.colors[i][0], data.colors[i][1], data.colors[i][2]
}
);
}
const SDL_PixelFormatDetails *format = SDL_GetPixelFormatDetails(SDL_PIXELFORMAT_RGBA32);
for (int y = 0; y < 480; y++)
for (int x = 0; x < 640; x++)
data.mcbuffer[y][x] = SDL_MapRGB(
format, NULL,
(uint8_t) data.cbuffer[y][x][0],
(uint8_t) data.cbuffer[y][x][1],
(uint8_t) data.cbuffer[y][x][2]
);
int pitch;
void *pixels = NULL;
SDL_LockTexture(texture, NULL, &pixels, &pitch);
memcpy(pixels, data.cbuffer, 640 * 480 * sizeof(vec3));
pitch = 640 * sizeof(vec3);
SDL_UnlockTexture(texture);
SDL_RenderTexture(renderer, texture, NULL, NULL);
memset(data.mcbuffer, 0, 640 * 480 * sizeof(uint32_t));
memset(data.cbuffer, 0, 640 * 480 * sizeof(vec3));
for (int y = 0; y < 480; y++)
for (int x = 0; x < 640; x++)
data.dbuffer[y][x] = INFINITY;
}
int main(int argc, char **argv)
{
for (int y = 0; y < 480; y++)
for (int x = 0; x < 640; x++)
data.dbuffer[y][x] = INFINITY;
int status = parse_obj("../../monkey.obj");
if (!status)
return 1;
status = init();
if (status != SDL_APP_CONTINUE)
return status;
SDL_Event e;
while ((status = app_iterate()) == SDL_APP_CONTINUE)
while (SDL_PollEvent(&e) != 0)
if ((status = app_event(&e)) != SDL_APP_CONTINUE)
return (status == SDL_APP_SUCCESS) ? 0 : status;
}