Ray Tracing Part Three

We will add an other rectangle, shadow and animated camera in this part.

To use an arbitrary number of rectangles we will need a rectangle structure and an array that holds them. A rectangle is defined by it's base point, side point and it's normal vector. We will pre-calculate the width and height of the rectangle. We need members for transparency, diffuse and specular color also.

#include <stdio.h>

typedef struct
{

    v3_t base_p;
    v3_t side_p;
    v3_t norm_v;

    float wth;
    float hth;

    float transparency_c;

    uint32_t col_diff_u;
    uint32_t col_spec_u;

} rect_t;

int rect_cnt_i = 2;
rect_t rectangles[ 2 ];

With this we can organize intersection detection to a separate function that returns the nearest rect intersecting with the given line.

#include <stdio.h>

nearest_res_t get_nearest_rect( v3_t start_p , v3_t end_p , rect_t* exclude_r )
{

    nearest_res_t result = { 0 };
    float dist_f = FLT_MAX;

    if ( rect_cnt_i > 0 )
    {

        // iterate through all rectangles

        for ( int index_r = 0 ; index_r < rect_cnt_i ; index_r ++ )
        {

            rect_t* rect = &rectangles[ index_r ];

            if ( rect == exclude_r ) continue;

            // project ray from camera focus point through window grid point to square plane

            v3_t isect_p = line_plane_intersection( start_p , end_p , rect->base_p , rect->norm_v );

            if ( isect_p.x != FLT_MAX )
            {

                // let's find if intersection point is in the rectangle

                v3_t proj_p = point_line_projection( rect->side_p , rect->base_p , isect_p );

                // check x and y distance of intersection point from square center

                float dist_x = v3_length_squared( v3_sub( proj_p , rect->base_p ) );
                float dist_y = v3_length_squared( v3_sub( proj_p , isect_p ) );

                // compare squared distances with squared distances of rectangle

                if ( dist_x < ( rect->wth / 2.0 ) * ( rect->wth / 2.0 ) && 
                     dist_y < ( rect->hth / 2.0 ) * ( rect->hth / 2.0 ) )
                {
                    // cross point is inside square, let's calculate it's color based on light reflection angle

                    float distance = v3_length_squared( v3_sub( isect_p , start_p ) );

                    if ( distance < dist_f )
                    {

                        result.rect = rect;
                        result.isect_p = isect_p;

                        dist_f = distance;

                    }

                }

            }

        }

    }

    return result;

}

To introduce shadows we need to modify the window grid iteration loop a little. If there is an obstacle ( rectangle ) between the intersection point and the light we set that point to the shadow color, if not we calculate diffuse and specular color.

#include <stdio.h>

// create rays going through the camera window quad starting from the top left corner

for ( int row_i = 0 ; row_i < grid_rows_i ; row_i++ )
{

    for ( int col_i = 0 ; col_i < grid_cols_i ; col_i++ )
    {

        v3_t window_grid_v = camera_target_p;

        window_grid_v = v3_add( window_grid_v , v3_scale( window_stepx_v , grid_cols_i / 2 - col_i ) );
        window_grid_v = v3_add( window_grid_v , v3_scale( window_stepy_v , - grid_rows_i / 2 + row_i ) );

        // ray/pixel location on screen

        int screen_grid_x = screen_step_size_f * col_i;
        int screen_grid_y = screen_step_size_f * row_i;

        // check for intersection

        uint32_t color = 0x000000FF;    // background color

        nearest_res_t result = get_nearest_rect( camera_focus_p , window_grid_v , NULL );

        if ( result.rect != NULL )
        {

            // check for direct connection with light for diffuse color

            nearest_res_t blocker_r = get_nearest_rect( result.isect_p , light_p , result.rect );

            if ( blocker_r.rect == NULL ) 
            {
                // diffuse color

                color = result.rect->col_diff_u;

                // specular color

                // mirror light point on normal vector to get perfect reflection

                v3_t light_mirr_p = point_line_mirror( result.isect_p , v3_add( result.isect_p , result.rect->norm_v ) , light_p );

                v3_t tofocus = v3_sub( camera_focus_p , result.isect_p );
                v3_t tomirrored = v3_sub( light_mirr_p , result.isect_p );

                float angle = v3_angle( tomirrored , tofocus );

                // the smaller the angle the closer the mirrored ray and the real ray are - reflection 

                float colorsp_f = ( float ) 0xFF * ( ( M_PI - angle ) / M_PI );
                uint32_t colorsp_u = ( uint8_t ) colorsp_f;
                colorsp_u = colorsp_u << 24 | colorsp_u << 16 | colorsp_u << 8 | 0xFF;

                color = color_average( color , colorsp_u );

            }
            else 
            {
                // shadow

                color = 0x111111FF;
            }

            // if rect is transparent calculate refraction and check for further intersections

            // reflect ray on intersection point and check for further intersections

        }

        framebuffer_drawsquare( screen_grid_x , screen_grid_y , screen_step_size_f , color );

    }

}

Finally let's make the camera movable with the arrow keys. Let's put the whole grid iteration in an infinite loop and check for keypress in every iteration.

#include <stdio.h>

int code = getch( );

if ( code == 67 ) camera_focus_p.x += 10.0;
if ( code == 68 ) camera_focus_p.x -= 10.0;

Final result :

raytrace

Download the code : raytrace_part_three.c

Comment on Ray Tracing Part Three
comments powered by Disqus