I am trying to write a 3d Renderer in C using SDL and cgml but my shading seems to be not working correctly. When rendering the teapot I get seams and when I want to render a cube I get a bright white line between my triangles. I am pretty sure that I calculate my normals correctly but I can show that code too if necessary. All my math functions like v3_normalise / cam_perspective should be correct too because they are just aliases for cgml functions.
static void _draw_triangle(SDL_Renderer* renderer, int x1, int y1, int x2, int y2, int x3, int y3) {
SDL_RenderLine(renderer, x1, y1, x2, y2);
SDL_RenderLine(renderer, x2, y2, x3, y3);
SDL_RenderLine(renderer, x3, y3, x1, y1);
}
static int32_t _compare_triangles(const void* a, const void* b) {
triangle_t* triangle_a = (triangle_t*)a;
triangle_t* triangle_b = (triangle_t*)b;
float z1 = (triangle_a->vertices[0].z + triangle_a->vertices[1].z + triangle_a->vertices[2].z) / 3.0f;
float z2 = (triangle_b->vertices[0].z + triangle_b->vertices[1].z + triangle_b->vertices[2].z) / 3.0f;
float comparison = z2 - z1;
if (comparison < 0.0f) return 1;
if (comparison > 0.0f) return -1;
return 0;
}
static void _render_triangles_mesh(SDL_Renderer* renderer, triangle_t* triangles_to_render, size_t num_triangles_to_render) {
SDL_SetRenderDrawColor(renderer, 0xFF, 0, 0xFF, 0xFF); // set color to pink
for (size_t j = 0; j < num_triangles_to_render; j++) {
triangle_t triangle = dynamic_array_at(triangles_to_render, j);
_draw_triangle(
renderer,
triangle.vertices[0].x, triangle.vertices[0].y,
triangle.vertices[1].x, triangle.vertices[1].y,
triangle.vertices[2].x, triangle.vertices[2].y
);
}
}
static v3i _calc_vertex_color(v3 normal, v3 light_direction) {
float intensity = glm_max(0.1f, v3_dot(normal, light_direction)) * 1.5f;
v3i color = v3i_of((int32_t)glm_clamp(255.0f * intensity, 0.0f, 255.0f));
return color;
}
static void _render_triangles_filled(SDL_Renderer* renderer, triangle_t* triangles_to_render, size_t num_triangles_to_render, v3* vertex_normals, v3 light_direction) {
/* convert to SDL_Vertex triangles and add to vertices to render */
SDL_Vertex vertices[num_triangles_to_render * 3];
for (size_t i = 0; i < num_triangles_to_render; i++) {
triangle_t triangle = dynamic_array_at(triangles_to_render, i);
for (size_t j = 0; j < 3; j++) {
v3i color = _calc_vertex_color(vertex_normals[triangle.indices[j]], light_direction);
/* add vertex to SDL vertices */
SDL_Vertex vertex = {
.position = {triangle.vertices[j].x, triangle.vertices[j].y},
.color = {color.r, color.g, color.b, 0xFF}
};
vertices[i * 3 + j] = vertex;
}
}
/* render triangles */
SDL_RenderGeometry(renderer, NULL, vertices, num_triangles_to_render * 3, NULL, 0);
}
int32_t render(state_t* state, mesh_t* mesh, v3 object_offset) {
static float alpha = 0;
alpha += 0.6f * state->time.delta_sec;
m4 rotation_matrix = glms_euler_xyz(v3_of(0.0f, alpha, 0.0f)); // glms_euler_xyz(v3_of(alpha * 0.5f, 0.0f, alpha));
m4 translation_matrix = glms_translate_make(object_offset);
m4 world_matrix = m4_mul(translation_matrix, rotation_matrix);
v3 up = v3_of(0.0f, 1.0f, 0.0f);
v3 target = v3_add(state->engine.camera.position, state->engine.camera.direction);
m4 camera_matrix = cam_lookat(state->engine.camera.position, target, up);
m4 view_matrix = glms_inv_tr(camera_matrix);
triangle_t* triangles_to_render = dynamic_array_create(triangle_t);
for (size_t i = 0; i < mesh->num_triangles; i++) {
triangle_t triangle = dynamic_array_at(mesh->triangles, i);
triangle_t triangle_transformed, triangle_projected, triangle_viewed;
/* rotate and translate triangle */
triangle_transformed = triangle;
triangle_transformed.vertices[0] = m4_mulv(world_matrix, triangle.vertices[0]);
triangle_transformed.vertices[1] = m4_mulv(world_matrix, triangle.vertices[1]);
triangle_transformed.vertices[2] = m4_mulv(world_matrix, triangle.vertices[2]);
/* world space to camera space */
triangle_viewed = triangle_transformed;
triangle_viewed.vertices[0] = m4_mulv(view_matrix, triangle_transformed.vertices[0]);
triangle_viewed.vertices[1] = m4_mulv(view_matrix, triangle_transformed.vertices[1]);
triangle_viewed.vertices[2] = m4_mulv(view_matrix, triangle_transformed.vertices[2]);
/* 3d to 2d */
triangle_projected = triangle_viewed;
triangle_projected.vertices[0] = m4_mulv(state->engine.projection_matrix, triangle_viewed.vertices[0]);
triangle_projected.vertices[1] = m4_mulv(state->engine.projection_matrix, triangle_viewed.vertices[1]);
triangle_projected.vertices[2] = m4_mulv(state->engine.projection_matrix, triangle_viewed.vertices[2]);
triangle_projected.vertices[0] = v4_divs(triangle_projected.vertices[0], triangle_projected.vertices[0].w);
triangle_projected.vertices[1] = v4_divs(triangle_projected.vertices[1], triangle_projected.vertices[1].w);
triangle_projected.vertices[2] = v4_divs(triangle_projected.vertices[2], triangle_projected.vertices[2].w);
/* backface culling using winding order */
v3 line1 = v3_sub(v3_from(triangle_projected.vertices[1]), v3_from(triangle_projected.vertices[0]));
v3 line2 = v3_sub(v3_from(triangle_projected.vertices[2]), v3_from(triangle_projected.vertices[0]));
float sign = line1.x * line2.y - line2.x * line1.y;
if (sign > 0.0f) continue;
/* scale into view */
triangle_projected.vertices[0].x = map(triangle_projected.vertices[0].x, -1.0f, 1.0f, 0, WIDTH);
triangle_projected.vertices[0].y = map(triangle_projected.vertices[0].y, -1.0f, 1.0f, 0, HEIGHT);
triangle_projected.vertices[1].x = map(triangle_projected.vertices[1].x, -1.0f, 1.0f, 0, WIDTH);
triangle_projected.vertices[1].y = map(triangle_projected.vertices[1].y, -1.0f, 1.0f, 0, HEIGHT);
triangle_projected.vertices[2].x = map(triangle_projected.vertices[2].x, -1.0f, 1.0f, 0, WIDTH);
triangle_projected.vertices[2].y = map(triangle_projected.vertices[2].y, -1.0f, 1.0f, 0, HEIGHT);
/* add triangle to list */
dynamic_array_append(triangles_to_render, triangle_projected);
}
/* transform vertex normals */
v3* transformed_normals = malloc(sizeof(v3) * mesh->num_vertices);
m3 normal_matrix = m3_inv(m4_pick3t(world_matrix));
for (size_t i = 0; i < mesh->num_vertices; i++) {
v3 normal = dynamic_array_at(mesh->vertex_normals, i);
v3 normal_transformed = v3_normalize(m3_mulv(normal_matrix, normal));
transformed_normals[i] = normal_transformed;
}
/* sort triangles back to front */
size_t num_triangles_to_render = dynamic_array_get_length(triangles_to_render);
qsort(triangles_to_render, num_triangles_to_render, sizeof(triangle_t), _compare_triangles);
/* draw triangles */
#ifdef DEBUG
_render_triangles_mesh(state->renderer, triangles_to_render, num_triangles_to_render);
#else
_render_triangles_filled(state->renderer, triangles_to_render, num_triangles_to_render, transformed_normals, state->engine.light.direction);
#endif
/* cleanup */
free(transformed_normals);
dynamic_array_destroy(triangles_to_render);
return state->retval;
}
EDIT: Image of the normals