Skip to main content

Overview

Cub3D renders a 3D view by casting one ray per screen column and drawing vertical strips of textured walls. The rendering happens in real-time at the game’s frame rate, creating a smooth first-person perspective.

The Render Loop

The main rendering function is called every frame by MiniLibX:
utils6.c:74-91
int	cubed_render(t_map *mapa)
{
	mapa->new_x = mapa->xx;
	mapa->new_y = mapa->yy;
	move_cubed(mapa, mapa->dir_x, mapa->dir_y);
	rotate_cubed (mapa);
	mapa->map_x = mapa->xx;
	mapa->map_y = mapa->yy;
	if (mapa->starting_angle > 2 * PI)
		mapa->starting_angle = 0;
	if (mapa->starting_angle < 0)
		mapa->starting_angle = 2 * PI;
	set_player_dir_and_plane(mapa);
	clear_image(mapa);
	raycasting (mapa, -1);
	mlx_put_image_to_window(mapa->mlx_ptr, mapa->win_ptr, mapa->img_ptr, 0, 0);
	return (0);
}
1

Update player movement

Process WASD key inputs to move the player and arrow keys to rotate the view.
2

Normalize angle

Keep the viewing angle within [0, 2π] range.
3

Update direction vectors

Recalculate the direction and camera plane vectors based on the current angle.
4

Clear the frame buffer

Reset the image to prepare for new frame rendering.
5

Perform raycasting

Cast rays for every column and draw the results.
6

Display the frame

Push the rendered image to the window.
This function is registered as the loop hook:
main.c:96
mlx_loop_hook (mapa.mlx_ptr, cubed_render, &mapa);

Screen Resolution

The rendering resolution is defined as constants:
cub3.h:30-31
#define WIDTH 1732
#define HEIGHT 1000
  • Width: 1732 pixels - Each column represents one ray
  • Height: 1000 pixels - Determines vertical resolution
The aspect ratio is approximately 16:9 (1.732:1), which is close to standard widescreen.

Wall Height Calculation

Wall height on screen is inversely proportional to distance from the player:
raycasting.c:76
mapa->line_height = (int)(HEIGHT / *dist);
This creates the perspective effect:
  • Closer walls appear taller
  • Distant walls appear shorter
  • The division by perpendicular distance prevents fish-eye distortion

Calculating Draw Bounds

The vertical position where the wall should be drawn:
raycasting.c:119-124
start_y = (HEIGHT - mapa->line_height) / 2;
mapa->end = start_y + mapa->line_height;
if (start_y < 0)
	start_y = 0;
if (mapa->end > HEIGHT)
	mapa->end = HEIGHT;
  • Center the wall vertically on screen
  • Clamp to screen bounds to handle very close walls

Drawing a Vertical Strip

Each screen column is rendered in three parts:
drawing.c:52-66
void	draw_3d_dda(int x, int start_y, int end_y, t_map *mapa)
{
	int	y;

	y = -1;
	while (++y < start_y)
		put_pixel(x, y, 0x87CEEB, mapa);
	draw_wall (x, start_y, end_y, mapa);
	y = end_y;
	while (y < HEIGHT)
	{
		put_pixel(x, y, 0x444444, mapa);
		y++;
	}
}

Ceiling

Pixels above the wall (0 to start_y) are filled with color 0x87CEEB (sky blue).

Wall

Pixels from start_y to end_y are textured using the selected wall texture.

Floor

Pixels below the wall (end_y to HEIGHT) are filled with color 0x444444 (dark grey).

Pixel Operations

The put_pixel function writes directly to the image buffer:
utils7.c:228-229 (referenced from cub3.h)
void		put_pixel(int x, int y, int color, t_map *mapa);
This writes to the memory buffer retrieved during initialization:
main.c:68-70
mapa->img_ptr = mlx_new_image(mapa->mlx_ptr, WIDTH, HEIGHT);
mapa->pixel_ptr = mlx_get_data_addr(mapa->img_ptr, &mapa->bpp,
		&mapa->line_length, &mapa->endian);

Texture Coordinate Mapping

Horizontal (X) Texture Coordinate

Determined by where the ray hits the wall:
drawing.c:35-44
void	draw_wall(int x, int start_y, int end_y, t_map *mapa)
{
	double		wall_x;
	int			tex_x;

	wall_x = mapa->wall_x - floor(mapa->wall_x);
	tex_x = (int)(wall_x * mapa->current_tex->width);
	if ((mapa->side == 0 && mapa->ray_dir_x < 0)
		|| (mapa->side == 1 && mapa->ray_dir_y > 0))
		tex_x = mapa->current_tex->width - tex_x - 1;
  • Extract fractional part of hit position
  • Scale to texture width
  • Flip for certain orientations to maintain visual consistency

Vertical (Y) Texture Coordinate

Calculated for each pixel based on its screen position:
drawing.c:15-32
void	calculate_and_draw_pixel(t_map *mapa, int start_y, int tex_x, int x)
{
	int		offset;
	int		color;
	int		tex_y;

	mapa->d = start_y * 256 - HEIGHT * 128 + mapa->line_height * 128;
	tex_y = (mapa->d * mapa->current_tex->height) / mapa->line_height / 256;
	if (tex_x < 0 || tex_x >= mapa->current_tex->width || tex_y < 0
		|| tex_y >= mapa->current_tex->height)
		return ;
	offset = tex_y * mapa->current_tex->line_len + tex_x
		* (mapa->current_tex->bpp / 8);
	if (offset < 0 || offset + 3 >= mapa->current_tex->height
		* mapa->current_tex->line_len)
		return ;
	color = *(int *)(mapa->current_tex->data + offset);
	put_pixel(x, start_y, color, mapa);
}
The formula uses fixed-point arithmetic:
  • Multiply by 256 for precision
  • Scale by texture height and wall height
  • Divide by 256 to convert back to integer
The multiplication by 256 is crucial for maintaining precision in integer division. Without it, texture mapping would have visible artifacts.

Image Buffer Management

Creating the Frame Buffer

The image buffer is allocated once during initialization:
main.c:68-71
mapa->img_ptr = mlx_new_image(mapa->mlx_ptr, WIDTH, HEIGHT);
mapa->pixel_ptr = mlx_get_data_addr(mapa->img_ptr, &mapa->bpp,
		&mapa->line_length, &mapa->endian);
mlx_put_image_to_window(mapa->mlx_ptr, mapa->win_ptr, mapa->img_ptr, 0, 0);

Clearing the Buffer

At the start of each frame, the buffer can be cleared:
drawing.c:68-84
void	clear_image(t_map *mapa)
{
	int	y;
	int	x;

	y = 0;
	while (y < HEIGHT)
	{
		x = 0;
		while (x < WIDTH)
		{
			put_pixel(x, y, 0, mapa);
			x++;
		}
		y++;
	}
}
In practice, this clear operation may be skipped since every pixel is overwritten during rendering (ceiling, wall, or floor).

Raycasting Integration

The raycasting function processes all screen columns:
raycasting.c:102-127
void	raycasting(t_map *mapa, int i)
{
	double	camera_x;
	double	side_x;
	double	side_y;
	double	dist;
	int		start_y;

	while (++i < WIDTH)
	{
		camera_x = 2 * i / (double)WIDTH - 1;
		mapa->ray_dir_x = mapa->dir_x + mapa->plane_x * camera_x;
		mapa->ray_dir_y = mapa->dir_y + mapa->plane_y * camera_x;
		init_dda(mapa, &side_x, &side_y);
		perform_dda(mapa, &side_x, &side_y);
		calculate_wall_data(mapa, &dist);
		select_texture(mapa);
		start_y = (HEIGHT - mapa->line_height) / 2;
		mapa->end = start_y + mapa->line_height;
		if (start_y < 0)
			start_y = 0;
		if (mapa->end > HEIGHT)
			mapa->end = HEIGHT;
		draw_3d_dda(i, start_y, mapa->end, mapa);
	}
}
For each column (i from 0 to WIDTH-1):
  1. Calculate ray direction
  2. Perform DDA to find wall hit
  3. Calculate wall distance and height
  4. Select appropriate texture
  5. Calculate draw boundaries
  6. Render the column

Floor and Ceiling Rendering

Currently, floor and ceiling use solid colors:
drawing.c:57-65
// Ceiling
while (++y < start_y)
	put_pixel(x, y, 0x87CEEB, mapa);

// Floor
y = end_y;
while (y < HEIGHT)
{
	put_pixel(x, y, 0x444444, mapa);
	y++;
}

Using Parsed Colors

The map file defines floor and ceiling colors that are parsed:
main.c:63-64
mapa->floor_color = parse_color(mapa->floorcolor);
mapa->ceiling_color = parse_color(mapa->ceilingcolor);
To use these parsed colors instead of hardcoded values:
  • Replace 0x87CEEB with mapa->ceiling_color
  • Replace 0x444444 with mapa->floor_color

Performance Optimizations

Single Image Buffer

One frame buffer is reused every frame, avoiding repeated allocation.

Direct Memory Access

Pixel data is written directly to memory without function call overhead per pixel.

Integer Arithmetic

Uses fixed-point integer math instead of floating-point where possible.

Bounds Clamping

Clamps draw ranges to screen bounds to avoid unnecessary conditional checks in inner loops.

Rendering Pipeline Summary

Key Constants

From cub3.h:
cub3.h:30-34
#define WIDTH 1732
#define HEIGHT 1000
#define PI 3.14159265358979323846
#define SIZE 30
#define POV 1.17
  • WIDTH: Screen width and number of rays cast
  • HEIGHT: Screen height for vertical resolution
  • PI: Used for angle calculations
  • SIZE: Map tile size in pixels
  • POV: Field of view multiplier (≈66 degrees)

Next Steps

Raycasting

Dive deeper into the raycasting algorithm

Textures

Learn about texture mapping in detail