Skip to content

Rotating cube in software

Anaël Seghezzi edited this page Mar 25, 2020 · 2 revisions

Here we will see how to render a rotating cube in software (no GPU).

First, we create an empty image (our frame buffer):

#include <ctoy.h>

struct m_image image = M_IMAGE_IDENTITY();

void ctoy_begin(void)
{
   // create a 256 x 256 RGB image
   m_image_create(&image, M_FLOAT, 256, 256, 3);
}

void ctoy_end(void)
{
   // free the image
   m_image_destroy(&image);
}

void ctoy_main_loop(void)
{}

Then let's declare a cube vertices in 3d:

#include <ctoy.h>

struct m_image image = M_IMAGE_IDENTITY();

// a list of 8 vertices in the shape of a cube
float3 vertices[8] = {
   {-1, -1, -1},
   { 1, -1, -1},
   { 1,  1, -1},
   {-1,  1, -1},
   {-1, -1,  1},
   { 1, -1,  1},
   { 1,  1,  1},
   {-1,  1,  1}
};

void ctoy_begin(void)
{
   m_image_create(&image, M_FLOAT, 256, 256, 3);
}

void ctoy_end(void)
{
   m_image_destroy(&image);
}

void ctoy_main_loop(void)
{}

Let's add some simple 3d transformation with perspective projection and render the vertices as points:

#include <ctoy.h>

struct m_image image = M_IMAGE_IDENTITY();

float3 vertices[8] = {
   {-1, -1, -1},
   { 1, -1, -1},
   { 1,  1, -1},
   {-1,  1, -1},
   {-1, -1,  1},
   { 1, -1,  1},
   { 1,  1,  1},
   {-1,  1,  1}
};

void ctoy_begin(void)
{
   m_image_create(&image, M_FLOAT, 128, 128, 3);
}

void ctoy_end(void)
{
   m_image_destroy(&image);
}

void ctoy_main_loop(void)
{
   float3 tmp_vertices[8];
   float3 pos = {0, 0, 5}; // we want to move the cube in front of the camera
   int i;

   // transformation
   for (i = 0; i < 8; i++) {
      M_ADD3(tmp_vertices[i], vertices[i], pos); // translation
   }

   // simple perspective projection
   for (i = 0; i < 8; i++) {
      // we divide x and y coordinates by z coordinate and center the result
      tmp_vertices[i].x = (tmp_vertices[i].x / tmp_vertices[i].z) + 0.5f;
      tmp_vertices[i].y = (tmp_vertices[i].y / tmp_vertices[i].z) + 0.5f;
   }

   // clear the image to zero
   memset(image.data, 0, image.size * sizeof(float));

   // draw points
   for (i = 0; i < 8; i++) {

      // get the pixel coordinates
      int x = tmp_vertices[i].x * image.width;
      int y = tmp_vertices[i].y * image.height;

      // if the pixel is not out of range, draw a point
      if (x >= 0 && y >= 0 && x < image.width && y < image.height) {

         float *pixel = (float *)image.data + (y * image.width + x) * image.comp;
         pixel[0] = 1.0f; // R
         pixel[1] = 1.0f; // G
         pixel[2] = 1.0f; // B
      }
   }

   // send the image to the screen
   ctoy_swap_buffer(&image);
}

Finally let's add a rotation matrix with animation:

#include <ctoy.h>

struct m_image image = M_IMAGE_IDENTITY();

float3 vertices[8] = {
   {-1, -1, -1},
   { 1, -1, -1},
   { 1,  1, -1},
   {-1,  1, -1},
   {-1, -1,  1},
   { 1, -1,  1},
   { 1,  1,  1},
   {-1,  1,  1}
};

void ctoy_begin(void)
{
   m_image_create(&image, M_FLOAT, 128, 128, 3);
}

void ctoy_end(void)
{
   m_image_destroy(&image);
}

void ctoy_main_loop(void)
{
   float3 tmp_vertices[8];
   float3 pos = {0, 0, 5};
   int i;

   float matrix[16] = M_MAT4_IDENTITY(); // let's declare a proper matrix
   float t = (ctoy_t() % 200) / 200.0f; // get ctoy's tick to animate all this
   float3 rot = { // declare x, y, z Euler rotation
      t * M_PI * 2,
      t * M_PI * 2,
      0
   };
   
   m_mat4_rotation_euler(matrix, &rot); // create the rotation matrix
   m_mat4_translation(matrix, &pos); // set the matrix translation

   // transformation
   for (i = 0; i < 8; i++)
      m_mat4_transform3(&tmp_vertices[i], matrix, &vertices[i]);

   for (i = 0; i < 8; i++) {
      tmp_vertices[i].x = (tmp_vertices[i].x / tmp_vertices[i].z) + 0.5f;
      tmp_vertices[i].y = (tmp_vertices[i].y / tmp_vertices[i].z) + 0.5f;
   }

   memset(image.data, 0, image.size * sizeof(float));

   for (i = 0; i < 8; i++) {

      int x = tmp_vertices[i].x * image.width;
      int y = tmp_vertices[i].y * image.height;

      if (x >= 0 && y >= 0 && x < image.width && y < image.height) {

         float *pixel = (float *)image.data + (y * image.width + x) * image.comp;
         pixel[0] = 1.0f;
         pixel[1] = 1.0f;
         pixel[2] = 1.0f;
      }
   }

   ctoy_swap_buffer(&image);
   t++;
}

Let's have fun and create a Demo effect:

#include <ctoy.h>

struct m_image image = M_IMAGE_IDENTITY();

float3 vertices[8] = {
   {-1, -1, -1},
   { 1, -1, -1},
   { 1,  1, -1},
   {-1,  1, -1},
   {-1, -1,  1},
   { 1, -1,  1},
   { 1,  1,  1},
   {-1,  1,  1}
};

void ctoy_begin(void)
{
   m_image_create(&image, M_FLOAT, 128, 128, 3);
   memset(image.data, 0, image.size * sizeof(float)); // clear the image to zero
}

void ctoy_end(void)
{
   m_image_destroy(&image);
}

void ctoy_main_loop(void)
{
   float3 tmp_vertices[8];
   float3 pos = {0, 0, 5};
   int i;

   float matrix[16] = M_MAT4_IDENTITY();
   float t = (ctoy_t() % 200) / 200.0f;
   float3 rot = {
      t * M_PI * 2,
      t * M_PI * 2,
      0
   };
   
   m_mat4_rotation_euler(matrix, &rot);
   m_mat4_translation(matrix, &pos);

   for (i = 0; i < 8; i++)
      m_mat4_transform3(&tmp_vertices[i], matrix, &vertices[i]);

   for (i = 0; i < 8; i++) {
      tmp_vertices[i].x = (tmp_vertices[i].x / tmp_vertices[i].z) + 0.5f;
      tmp_vertices[i].y = (tmp_vertices[i].y / tmp_vertices[i].z) + 0.5f;
   }

   for (i = 0; i < image.size; i++) // fade to zero instead of clearing to zero 
      ((float *)image.data)[i] *= 0.8f;

   for (i = 0; i < 8; i++) {

      int x = tmp_vertices[i].x * image.width;
      int y = tmp_vertices[i].y * image.height;

      if (x >= 0 && y >= 0 && x < image.width && y < image.height) {

         float *pixel = (float *)image.data + (y * image.width + x) * image.comp;

         // add instead of setting
         pixel[0] += 0.5f;
         pixel[1] += 1.0f;
         pixel[2] += 0.5f;
      }
   }

   m_image_gaussian_blur(&image, &image, 1.5, 1.5); // blur !

   ctoy_swap_buffer(&image);
   t++;
}

Clone this wiki locally